package no.nordicsemi.android.nrfmesh.widgets;

import android.annotation.SuppressLint;
import android.Manifest;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.*;
import android.provider.Settings;
import android.text.TextUtils;
import android.graphics.*;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Handler;
import android.os.Looper;
import android.text.InputType;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import android.widget.*;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.MediatorLiveData;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import com.google.gson.Gson;
import com.google.android.material.button.MaterialButtonToggleGroup;
import dagger.hilt.android.AndroidEntryPoint;
import com.google.gson.reflect.TypeToken;
import no.nordicsemi.android.mesh.ApplicationKey;
import no.nordicsemi.android.mesh.NodeKey;
import no.nordicsemi.android.mesh.models.VendorModel;
import no.nordicsemi.android.mesh.transport.Element;
import no.nordicsemi.android.mesh.transport.ProvisionedMeshNode;
import no.nordicsemi.android.mesh.transport.VendorModelMessageAcked;
import no.nordicsemi.android.mesh.MeshManagerApi;
import no.nordicsemi.android.nrfmesh.LogRecordActivity;
import no.nordicsemi.android.mesh.transport.*;
import no.nordicsemi.android.nrfmesh.R;
import no.nordicsemi.android.nrfmesh.SentProfileAdapter;
import no.nordicsemi.android.nrfmesh.auth.session.SessionManager;
import no.nordicsemi.android.nrfmesh.auth.ui.AuthActivity;
import no.nordicsemi.android.nrfmesh.database.AppDatabase;
import no.nordicsemi.android.nrfmesh.database.entity.LogRecord;
import no.nordicsemi.android.nrfmesh.database.entity.ReceivedProfile;
import no.nordicsemi.android.nrfmesh.adapter.ExtendedBluetoothDevice;
import no.nordicsemi.android.nrfmesh.autobind.MeshAutoConfigurator;
import no.nordicsemi.android.nrfmesh.ble.BleMeshManager;
import no.nordicsemi.android.nrfmesh.ble.adapter.DevicesAdapter;
import no.nordicsemi.android.nrfmesh.database.entity.SentProfile;
import no.nordicsemi.android.nrfmesh.domain.DeviceStatus;
import no.nordicsemi.android.nrfmesh.domain.VendorModelData;
import no.nordicsemi.android.nrfmesh.domain.Wave;
import no.nordicsemi.android.nrfmesh.node.ModelConfigurationActivity;
import no.nordicsemi.android.nrfmesh.node.adapter.NodeAdapter;
import no.nordicsemi.android.nrfmesh.provisioners.MeshHeadlessProvisioner;
import no.nordicsemi.android.nrfmesh.utils.ProvisionerStates;
import no.nordicsemi.android.nrfmesh.utils.Utils;
import no.nordicsemi.android.nrfmesh.utils.ImpedanceConverter;
import no.nordicsemi.android.nrfmesh.utils.DataConversionTester;
import no.nordicsemi.android.nrfmesh.utils.ImpedanceDataFilter;
import no.nordicsemi.android.nrfmesh.viewmodels.*;
import no.nordicsemi.android.nrfmesh.viewmodels.ScannerLiveData;
import no.nordicsemi.android.nrfmesh.utils.NodeOnlineHelper;

import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.Executors;
import no.nordicsemi.android.nrfmesh.auth.ui.account.PersonalInfoDialogFragment;
import no.nordicsemi.android.nrfmesh.auth.ui.account.SwitchAccountDialogFragment;

@AndroidEntryPoint
public class CustomControlsActivity extends ModelConfigurationActivity {

    private static final String TAG = "CustomControls";
    private static final String PREFS_NAME = "custom_controls_prefs";
    private static final String KEY_DEVICE_SETTINGS = "device_settings_json";
    private static final String KEY_CURRENT_DEVICE_INDEX = "current_device_index";
    private static final String KEY_SELECTED_NODE_ADDRS = "selected_node_addrs_json";
    private static final String KEY_ASSIGNED_COLORS = "assigned_colors_json";
    private static final String KEY_NEXT_COLOR_TO_ASSIGN = "next_color_to_assign";

    // ─────────────── 档位 & 总段数上限 ───────────────
    private static final int GEAR_MAX = 40;
    private static final int TOTAL_SEG_MAX = 3;

    //  范围常量
    private static final int TOTAL_SEG_MIN = 1;    // 总段数最小 1
    private static final int GEAR_MIN = 1;    // 档位最小 1
    private static final int MIN_PULSE_WIDTH_US = 100;  // 脉宽最小 100 µs
    private static final int MAX_PULSE_WIDTH_US = 1000; // 脉宽最大 1000 µs
    private static final int MIN_FREQUENCY_HZ = 1;    // 频率最小 1 Hz
    private static final int MAX_FREQUENCY_HZ = 120;  // 频率最大 120 Hz

    // ─────────────── 默认值（按你的新要求） ───────────────
    private static final int DEFAULT_TOTAL_SEGMENTS = 1;    // 总段数初始 1
    private static final int DEFAULT_SEGMENT_REPEAT = 0;    // 保留字段
    private static final int DEFAULT_DURATION_MS = 0;    // 段时长 0ms
    private static final int DEFAULT_PULSE_WIDTH_US = 100;  // 脉宽 100µs
    private static final int DEFAULT_FREQUENCY_HZ = 1;    // 频率 1Hz
    private static final int DEFAULT_WAVEFORM_REPEAT = 0;    // 保留字段
    private static final int DEFAULT_WAVEFORM_TYPE = 0;    // Spinner index
    private static final int DEFAULT_GEAR = 1;    // 档位 1
    private static final int DEFAULT_TIME_MIN = 0;    // 倒计时分钟 0

    // 左侧控件
    private Button btnPlay, btnReadData, btnBluetoothMesh;
    private Spinner deviceSpinner;
    private TextView tvOnOff, tvGear, tvVoltage1, tvVoltage0, tvSoc, tvCurrent, tvResistance;

    // 总段数
    private Button btnTotalSegmentsMinus, btnTotalSegmentsPlus;
    private TextView tvTotalSegmentsValue;

    // 波形设置控件（保留：段时长、波形种类、脉宽、频率）
    private Button btnDurationMinus, btnDurationPlus;
    private TextView tvDurationValue;
    private Spinner spinnerWaveformType;
    private Button btnPulseWidthMinus, btnPulseWidthPlus;
    private TextView tvPulseWidthValue;
    private Button btnFrequencyMinus, btnFrequencyPlus;
    private TextView tvFrequencyValue;

    // 底部档位 & 时间
    private Button btnGearMinus, btnGearPlus;
    private TextView tvGearValue;

    // —— 输出波形缓慢右移 动画字段 ——
    private final android.os.Handler waveAnimHandler = new android.os.Handler(android.os.Looper.getMainLooper());
    private java.lang.Runnable waveAnimRunnable;
    private float waveScrollOffsetPx = 0f;
    private boolean waveAnimRunning = false;
    private Button btnTimeMinus, btnTimePlus;
    private TextView tvTimeValue;

    // 输出波形 & 保存
    private ImageView imgOutputWaveform;
    private Button btnSaveData;

    // 段序号标签容器 & 当前激活段
    private LinearLayout segmentTabsContainer;
    private int selectedSegmentIndex = 1;
    private Map<Integer, SegmentParams> segmentParams = new HashMap<>();

    // 文本输入的波段组合
    private LinearLayout orderInputContainer;
    private EditText etBandOrder;
    private Button btnApplyOrder;
    private List<Integer> bandOrder = new ArrayList<>();

    // 计时相关
    private boolean isCounting = false;
    private long remainingMs = 0L;
    private CountDownTimer countDownTimer;

    // 设备管理
    private int currentDeviceIndex = -1;
    private String opCode;
    private final Map<String, ParameterState> deviceSettings = new HashMap<>();
    private final Map<Integer, ParameterState> legacyIndexSettingsPending = new HashMap<>();
    // 占位符移除，避免语法错误
    private int currentStatus = 0; // 0 表示暂停，1 表示运行

    // 节点列表相关
    private SharedViewModel mViewModel;
    private NodeAdapter mNodeAdapter;
    private MeshAutoConfigurator mMeshAutoConfigurator;
    private RecyclerView mNodesRecyclerView;
    private TextView mEmptyNodesText;

    //蓝牙权限
    private static final int REQUEST_BLUETOOTH_PERMISSIONS = 1001;

    // 可用节点列表
    private RecyclerView mAvailableNodesRecyclerView;
    private TextView mEmptyAvailableNodesText;
    private DevicesAdapter mAvailableDevicesAdapter;

    private MeshHeadlessProvisioner headless;
    //仅用于控制UI是否允许再次点击
    private final java.util.concurrent.atomic.AtomicBoolean mProvisioningActive = new java.util.concurrent.atomic.AtomicBoolean(false);

    // 扫描 VM
    private ScannerViewModel mScannerVM;

    // 当前配置名称
    private String currentConfigName;

    // 当前正在配置的设备
    private AtomicReference<ExtendedBluetoothDevice> mCurrentProvisioningDevice = new AtomicReference<>();
    // 记录本次配网时给设备生成的名字，用于在节点真的出现后兜底解锁
    @Nullable private volatile String mLastProvisioningName = null;
    // 新增：最近一次配网进度状态与连接状态（用于失败提示与日志收集）
    @Nullable private volatile ProvisionerStates mLastProvisionerState = null;
    @Nullable private volatile String mLastConnectionState = null;
    /** 统一的配网结束清理（成功/失败/取消都用它） */
    private void clearProvisioningLock(@Nullable String name, boolean restartScan) {
        // 1) 解除占用标记
        mCurrentProvisioningDevice.set(null);

        // 3) 标记 UI 不再占用
        mProvisioningActive.set(false);

        // 4) 关掉忙碌提示
        setBusyUi(false, null);

        // 5) 清理“延迟可见”队列
        if (!TextUtils.isEmpty(name)) {
            Runnable r = mPendingRunnables.remove(name);
            if (r != null) mDelayHandler.removeCallbacks(r);
            mPendingVisibility.remove(name);
            // 立即刷新过滤后的节点列表，避免新节点长时间不显示
            refreshFilteredNodes();
        }

        // 6) 需要的话重开扫
        if (restartScan) maybeStartScan();

        // 7) 清掉记录名
        mLastProvisioningName = null;
    }

    // 新增：用于对已配网节点列表应用延迟过滤的 LiveData 封装与状态
    private MediatorLiveData<List<ProvisionedMeshNode>> mFilteredNodes;
    private final Map<String, Long> mPendingVisibility = new HashMap<>();
    private final Map<String, Runnable> mPendingRunnables = new HashMap<>();
    private final Handler mDelayHandler = new Handler(Looper.getMainLooper());
    private final Map<String, Integer> pendingColorRetryCount = new HashMap<>();
    private static final int MAX_COLOR_RETRY = 3;
    private static final long COLOR_RETRY_BASE_DELAY_MS = 300L;

    private SentProfileViewModel viewModel;
    // 用 BaseViewModel
    // —— 刺激方式（经皮/经针）——
    private static final int STIM_MODE_TP = 0;  // 经皮
    private static final int STIM_MODE_ND = 1;  // 经针
    private int stimMode = STIM_MODE_TP;        // 当前选择，默认经皮
    private MaterialButtonToggleGroup tgStimMode; // 对应 XML 里的 @id/tgStimMode

    // Mesh组网功能相关
    private final List<ProvisionedMeshNode> selectedNodes = new ArrayList<>();
    private final Set<ProvisionedMeshNode> blockedNodes = new HashSet<>();
    private ArrayAdapter<String> deviceSpinnerAdapter;
    private final Map<Integer, ProvisionedMeshNode> spinnerIndexToNodeMap = new LinkedHashMap<>();
    private final List<String> deviceSpinnerItems = new ArrayList<>();
    // —— 颜色首次点击分配相关状态 ——
    private final Map<String, Integer> firstClickAssignedColor = new HashMap<>();
    private int nextColorToAssign = 1; // 1..6 循环分配
    private boolean isSpinnerUserTouch = false; // 区分用户点击触发 vs 程序设置触发

    // —— 统一的在线识别辅助（方案B） ——
    private NodeOnlineHelper onlineHelper;
    private final Integer DelayTime_ms = 150;

    // —— 代理节点在线扫描（短时脉冲） ——
    // 扫描回调：识别 Node Identity 并建立在线地址集合
    // 已切换为 NodeOnlineHelper 管理代理识别，无需本地 ScanCallback

    // 已切换为 NodeOnlineHelper 管理脉冲，无需本地 ticker

    // 已切换为 NodeOnlineHelper 管理扫描脉冲

    @SuppressLint("MissingInflatedId")
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_user_chip, menu);
        bindUserChip(menu);          // 绑定名字/角色 & 点击
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        bindUserChip(menu);          // 从其他页返回时刷新
        return super.onPrepareOptionsMenu(menu);
    }

    @Override
    protected void onResume() {
        super.onResume();
        invalidateOptionsMenu();     // 触发 onPrepareOptionsMenu

        // —— 启动输出波形缓慢右移动画 ——
        if (waveAnimRunnable == null) {
            waveAnimRunnable = new Runnable() {
                @Override public void run() {
                    if (!waveAnimRunning) return;
                    waveScrollOffsetPx += 10f; // 每帧右移像素
                    drawCombinedWaveform();
                    waveAnimHandler.postDelayed(this, 20); // ~50fps
                }
            };
        }
        waveAnimRunning = true;
        waveAnimHandler.postDelayed(waveAnimRunnable, 20); // 稍候开始，避免布局未就绪
    }

    /** 同时兼容两种情况：有 actionView（Chip）和无 actionView（文字按钮） */
    private void bindUserChip(Menu menu) {
        MenuItem item = menu.findItem(R.id.menu_user);
        if (item == null) return;

        View chip = item.getActionView(); // 若 XML 正确，这里非 null
        if (chip != null) {
            TextView tvName = (TextView) chip.findViewById(R.id.tvName);
            TextView tvRole = (TextView) chip.findViewById(R.id.tvRole);

            String name = "未登录";
            String roleSuffix = "";
            try {
                SessionManager s = new SessionManager(this);
                String n = s.name();
                String r = s.role();
                if (n != null && n.length() > 0) name = n;
                if (r != null && r.length() > 0) roleSuffix = " (" + r + ")";
            } catch (Exception ignored) {}

            tvName.setText(name);
            tvRole.setText(roleSuffix);

            chip.setOnClickListener(new View.OnClickListener() {
                @Override public void onClick(View v) { showUserMenu(v); }
            });
        } else {
            // 兜底：如果 actionView 仍然是 null（例如 XML 写错了），
            // 就让点击“文字按钮”也能弹菜单
            item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
                @Override public boolean onMenuItemClick(MenuItem mi) {
                    View anchor = findViewById(R.id.toolbar);
                    showUserMenu(anchor != null ? anchor : getWindow().getDecorView());
                    return true;
                }
            });
        }
    }

    private void showUserMenu(View anchor) {
        PopupMenu pm = new PopupMenu(this, anchor);
        pm.getMenuInflater().inflate(R.menu.menu_user_popup, pm.getMenu());
        pm.setOnMenuItemClickListener(item -> {
            int id = item.getItemId();
            if (id == R.id.act_profile) {
                // 个人信息：按当前会话角色显示
                PersonalInfoDialogFragment.newInstance()
                        .show(getSupportFragmentManager(), "personal_info");
                return true;

            } else if (id == R.id.act_switch) {
                // 切换账户：列出本机账户，点选后用已保存密码直接登录
                SwitchAccountDialogFragment.newInstance()
                        .show(getSupportFragmentManager(), "switch_account");
                return true;

            } else if (id == R.id.act_logout) {
                // 退出登录：清会话 + 仅本次跳过自动登录 + 清栈跳转登录页
                SessionManager sm = new SessionManager(CustomControlsActivity.this);
                sm.clearAndSkipAutoLoginOnce();
                startActivity(AuthActivity.intentFromLogout(
                        CustomControlsActivity.this, /*toRegister=*/false));
                finish();
                return true;

            } else if (id == R.id.act_dev_unlock) {
                // TODO: 解锁开发者（隐藏）——留到后续实现
                return true;
            }
            return false;
        });
        pm.show();
    }



    @SuppressLint("ClickableViewAccessibility")
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 1) 先加载布局
        setContentView(R.layout.custom_controls);

        // 2) 再拿到并挂上 Toolbar
        androidx.appcompat.widget.Toolbar toolbar =
                (androidx.appcompat.widget.Toolbar) findViewById(R.id.toolbar);
        if (toolbar == null) {
            // 如果这里仍为 null，说明布局里没有 id=toolbar，或布局不是你期望的那个文件
            android.util.Log.e("CustomControls", "Toolbar is null! Check layout/id.");
        } else {
            setSupportActionBar(toolbar);
            if (getSupportActionBar() != null) {
                getSupportActionBar().setTitle(""); // 留出右侧空间
            }
        }
        // 3) 下面再做所有 findViewById 与初始化
        //opCode = getIntent().getStringExtra("EXTRA_OPCODE");
        Log.d(TAG, "收到的 opCode: " + opCode);
        SharedPreferences prefs = getSharedPreferences("AppPrefs", MODE_PRIVATE);
        opCode = prefs.getString("opCode", "0xC0");

        // —— 左侧初始化 ——
        btnPlay = findViewById(R.id.btnPlay);
        btnReadData = findViewById(R.id.btnReadData);
        //btnBluetoothMesh = findViewById(R.id.btnBluetoothMesh);
        deviceSpinner = findViewById(R.id.deviceSpinner);

        tvOnOff = findViewById(R.id.tvOnOff);
        tvGear = findViewById(R.id.tvGear);
        tvCurrent = findViewById(R.id.tvCurrent);
        tvVoltage0 = findViewById(R.id.tvVoltage0);
        tvVoltage1 = findViewById(R.id.tvVoltage1);
        tvSoc = findViewById(R.id.tvSoc);
        tvResistance = findViewById(R.id.tvResistance);

        // —— 总段数 ——
        btnTotalSegmentsMinus = findViewById(R.id.btnTotalSegmentsMinus);
        btnTotalSegmentsPlus = findViewById(R.id.btnTotalSegmentsPlus);
        tvTotalSegmentsValue = findViewById(R.id.tvTotalSegmentsValue);

        // —— 波形设置（保留四项） ——
        btnDurationMinus = findViewById(R.id.btnDurationMinus);
        tvDurationValue = findViewById(R.id.tvDurationValue);
        btnDurationPlus = findViewById(R.id.btnDurationPlus);
        spinnerWaveformType = findViewById(R.id.spinnerWaveformType);
        btnPulseWidthMinus = findViewById(R.id.btnPulseWidthMinus);
        tvPulseWidthValue = findViewById(R.id.tvPulseWidthValue);
        btnPulseWidthPlus = findViewById(R.id.btnPulseWidthPlus);
        btnFrequencyMinus = findViewById(R.id.btnFrequencyMinus);
        tvFrequencyValue = findViewById(R.id.tvFrequencyValue);
        btnFrequencyPlus = findViewById(R.id.btnFrequencyPlus);

        // —— 刺激方式（经皮 / 经针）——
        tgStimMode = findViewById(R.id.tgStimMode);
        if (tgStimMode != null) {
            // 默认选中“经皮”
            tgStimMode.check(R.id.btnTranscutaneous);
            stimMode = STIM_MODE_TP;

            tgStimMode.addOnButtonCheckedListener((group, checkedId, isChecked) -> {
                if (!isChecked) return;
                stimMode = (checkedId == R.id.btnNeedle) ? STIM_MODE_ND : STIM_MODE_TP;

                // 如需联动限制参数范围，可在此调用自定义方法：
                // onStimModeChanged();
            });
        }


        // —— 底部档位 & 时间 ——
        btnGearMinus = findViewById(R.id.btnGearMinus);
        btnGearPlus = findViewById(R.id.btnGearPlus);
        tvGearValue = findViewById(R.id.tvGearValue);
        btnTimeMinus = findViewById(R.id.btnTimeMinus);
        btnTimePlus = findViewById(R.id.btnTimePlus);
        tvTimeValue = findViewById(R.id.tvTimeValue);

        // —— 输出 & 保存 ——
        imgOutputWaveform = findViewById(R.id.imgOutputWaveform);
        btnSaveData = findViewById(R.id.btnSaveData);

        // —— 段序号标签容器 ——
        segmentTabsContainer = findViewById(R.id.segmentTabsContainer);

        // —— 文本输入组合 ——
        orderInputContainer = findViewById(R.id.orderInputContainer);
        etBandOrder = findViewById(R.id.etBandOrder);
        btnApplyOrder = findViewById(R.id.btnApplyOrder);

        // 可用节点 RecyclerView
        mAvailableNodesRecyclerView = findViewById(R.id.availableNodesRecyclerView);
        mEmptyAvailableNodesText = findViewById(R.id.emptyAvailableNodesText);

        // 节点列表相关初始化
        mNodesRecyclerView = findViewById(R.id.nodesRecyclerView);
        mEmptyNodesText = findViewById(R.id.emptyNodesText);

        mViewModel = new ViewModelProvider(this).get(SharedViewModel.class);
        mMeshAutoConfigurator = MeshAutoConfigurator.getInstance();
        // 观察连接状态，便于在配网失败时提示与定位问题
        mViewModel.getConnectionState().observe(this, state -> {
            mLastConnectionState = state;
        });

        // 初始化在线识别辅助（方案B：复用通用方法）
        onlineHelper = new NodeOnlineHelper(
                this,
                mViewModel,
                mDelayHandler,
                () -> !mProvisioningActive.get()
        );
        onlineHelper.setOnlineUpdatedCallback(() -> {
            // 在线集合更新后：清理选中并刷新列表
            pruneSelectionForOfflineNodes();
            refreshFilteredNodes();
        });

        // 观察代理连接与地址：转调到通用辅助
        mViewModel.isConnectedToProxy().observe(this, isConnected -> {
            onlineHelper.onProxyConnectionChanged(isConnected);
        });
        mViewModel.getConnectedProxyAddress().observe(this, addr -> {
            onlineHelper.onConnectedProxyAddress(addr);
        });

        // —— 绑定过滤后的节点 LiveData ——
        mFilteredNodes = new MediatorLiveData<>();
        // 来源1：仓库提供的节点列表（常规刷新）
        mFilteredNodes.addSource(mViewModel.getNodes(), nodes -> {
            final List<ProvisionedMeshNode> filtered = onlineHelper.filterOnlineWithDelay(nodes, mPendingVisibility, DelayTime_ms);
            onlineHelper.setFilteredNodesWithOfflineToast(mFilteredNodes, filtered);
        });
        // 来源2：MeshNetwork 刷新事件（确保配网完成后的网络刷新也能驱动节点列表更新）
        mFilteredNodes.addSource(mViewModel.getNetworkLiveData(), live -> {
            if (live != null && live.getMeshNetwork() != null) {
                List<ProvisionedMeshNode> nodes = new ArrayList<>();
                for (final ProvisionedMeshNode node : live.getMeshNetwork().getNodes()) {
                    if (!node.getUuid().equalsIgnoreCase(live.getMeshNetwork().getSelectedProvisioner().getProvisionerUuid())) {
                        nodes.add(node);
                    }
                }
                final List<ProvisionedMeshNode> filtered = onlineHelper.filterOnlineWithDelay(nodes, mPendingVisibility, DelayTime_ms);
                onlineHelper.setFilteredNodesWithOfflineToast(mFilteredNodes, filtered);
            }
        });

        // 配置RecyclerView（使用过滤后的 LiveData）
        mNodeAdapter = new NodeAdapter(this, mFilteredNodes);
        // 挂载当前已分配的颜色映射，使节点列表渲染颜色
        mNodeAdapter.setAssignedColors(firstClickAssignedColor);
        mNodeAdapter.setOnItemClickListener(new NodeAdapter.OnItemClickListener() {
            @Override
            public void onConfigureClicked(ProvisionedMeshNode node) {
                handleNodeClick(node);
            }
        });
        // 接入删除点击回调：弹出确认对话框并删除节点
        mNodeAdapter.setOnDeleteClickListener((position, node) -> {
            new AlertDialog.Builder(CustomControlsActivity.this)
                    .setIcon(R.drawable.ic_delete)
                    .setTitle(R.string.title_delete_node)
                    .setMessage(R.string.delete_node_rationale)
                    .setNegativeButton(R.string.no, (dialog, which) -> {
                        mNodeAdapter.notifyItemChanged(position);
                    })
                    .setPositiveButton(R.string.yes, (dialog, which) -> {
                        final ProvisionedMeshNode target = mNodeAdapter.getItem(position);
                        if (target == null) {
                            Toast.makeText(CustomControlsActivity.this, "节点不存在或已被删除", Toast.LENGTH_SHORT).show();
                            mNodeAdapter.notifyItemChanged(position);
                            return;
                        }
                        final boolean deleted = mViewModel.getMeshManagerApi().getMeshNetwork().deleteNode(target);
                        if (deleted) {
                            final ProvisionedMeshNode selected = mViewModel.getSelectedMeshNode().getValue();
                            if (selected != null && selected.getUnicastAddress() == target.getUnicastAddress()) {
                                mViewModel.clearSelectedMeshNode();
                                mNodeAdapter.setSelectedNode(null);
                            }
                            Toast.makeText(CustomControlsActivity.this, getString(R.string.node_deleted), Toast.LENGTH_SHORT).show();
                        } else {
                            Toast.makeText(CustomControlsActivity.this, "节点删除失败", Toast.LENGTH_LONG).show();
                            mNodeAdapter.notifyItemChanged(position);
                        }
                    })
                    .show();
        });

        mNodesRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mNodesRecyclerView.setAdapter(mNodeAdapter);
        mNodesRecyclerView.setNestedScrollingEnabled(true);
        mNodesRecyclerView.setHasFixedSize(false);

        // 观察节点数据变化，控制空状态显示（基于过滤后的列表）
        mFilteredNodes.observe(this, nodes -> {
            if (nodes != null && !nodes.isEmpty()) {
                mNodesRecyclerView.setVisibility(View.VISIBLE);
                mEmptyNodesText.setVisibility(View.GONE);
            } else {
                mNodesRecyclerView.setVisibility(View.GONE);
                mEmptyNodesText.setVisibility(View.VISIBLE);
            }
        });

        // 兜底解锁观察者(可用节点列表)
        mViewModel.getNodes().observe(this, nodes -> {
            if (nodes == null || TextUtils.isEmpty(mLastProvisioningName)) return;

            // 修复：移除错误的早退，确保在节点列表出现刚配置完成的节点时清理配置锁
            for (ProvisionedMeshNode n : nodes) {
                if (mLastProvisioningName.equals(n.getNodeName())) {
                    // 如果 headless 正在运行，则不做兜底清锁，避免打断正常配网流程
                    if (headless != null && headless.isRunning()) {
                        return;
                    }
                    clearProvisioningLock(mLastProvisioningName, /*restartScan=*/true);
                    break;
                }
            }
        });

        // 观察网络加载状态，在网络加载完成后自动初始化应用密钥
        mViewModel.getNetworkLiveData().observe(this, network -> {
            if (network != null) {
                // 网络已加载，自动初始化应用密钥
                autoInitializeAppKey();
            }
        });

        //检查蓝牙权限
        checkAndRequestBluetoothPermissions();

        mScannerVM = new ViewModelProvider(this).get(ScannerViewModel.class);

        // 复用 ScannerActivity 同款适配器
        mAvailableDevicesAdapter = new DevicesAdapter(this, mScannerVM.getScannerRepository().getScannerResults());
        mAvailableDevicesAdapter.setOnItemClickListener(device -> {
            // 检查是否存在“占用”但实际未运行的旧锁，及时清理以避免点击无效
            if (mProvisioningActive.get() && (headless == null || !headless.isRunning())) {
                clearProvisioningLock(null, /*restartScan=*/true);
            }

            // 正在配网时拦截点击
            if ((headless != null && headless.isRunning()) || mProvisioningActive.get()) {
                Toast.makeText(this, "正在配置其他节点，请稍候", Toast.LENGTH_SHORT).show();
                return;
            }

            if (mViewModel.getBleMeshManager().isConnected()) mViewModel.disconnect();

            // 保存当前正在配置的设备引用
            mCurrentProvisioningDevice.set(device);

            // —— 记录延迟显示 ——
            final String pendingName = makeDefaultNodeName(device);
            mLastProvisioningName = pendingName;   //记录本次要配网的名字
            long nowTs = SystemClock.uptimeMillis();
            mPendingVisibility.put(pendingName, nowTs);
            Runnable r = new Runnable() {
                @Override public void run() {
                    mPendingVisibility.remove(pendingName);
                    refreshFilteredNodes();
                }
            };
            mPendingRunnables.put(pendingName, r);
            mDelayHandler.postDelayed(r, DelayTime_ms);

            // 立即从可用设备列表中移除该设备
            ScannerLiveData scannerLiveData = mScannerVM.getScannerRepository().getScannerResults().getValue();
            if (scannerLiveData != null) {
                List<ExtendedBluetoothDevice> devices = scannerLiveData.getDevices();
                devices.remove(device);
                mAvailableDevicesAdapter.notifyDataSetChanged();
            }

            Toast.makeText(this, "正在配置节点:" + pendingName + "，请等待", Toast.LENGTH_SHORT).show();

            MeshHeadlessProvisioner.Options opts = MeshHeadlessProvisioner.Options.noOob(pendingName);
            headless.start(device, opts);
        });

        mAvailableNodesRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mAvailableNodesRecyclerView.setAdapter(mAvailableDevicesAdapter);
        mAvailableNodesRecyclerView.setNestedScrollingEnabled(false);   //关闭嵌套滚动，避免父容器抢触摸
        mAvailableNodesRecyclerView.setHasFixedSize(false);
        // 按下时立即停止滚动，提升点击响应
        mAvailableNodesRecyclerView.setOnTouchListener((v, event) -> {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                mAvailableNodesRecyclerView.stopScroll();
            }
            return false;
        });

        // 用扫描结果(列表)切空态
        mScannerVM.getScannerRepository().getScannerResults().observe(this, results -> {
            boolean has = results != null && !results.isEmpty();
            mAvailableNodesRecyclerView.setVisibility(has ? View.VISIBLE : View.GONE);
            mEmptyAvailableNodesText.setVisibility(has ? View.GONE : View.VISIBLE);
            mAvailableDevicesAdapter.notifyDataSetChanged();
        });

        // 监听状态变化：一有条件就尝试开扫
        mScannerVM.getScannerRepository().getScannerState().observe(this, state -> {
            maybeStartScan();
        });

        // 初始化下拉 & 按钮
        initDeviceSpinner();
        initWaveformTypeSpinner();

        // —— 把所有 stepper 按钮的点击 + 长按加速 ——
        setRepeatListener(btnTotalSegmentsMinus, () -> {
            deleteCurrentSegmentParams();
            resetBandOrder();  //重置 band 顺序
            updateOrderInputVisibility();  //根据新总段数更新 UI 显示
        });
        setRepeatListener(btnTotalSegmentsPlus, () -> {
            saveCurrentSegmentParams();
            adjustValue(tvTotalSegmentsValue, +1);  // 总段数加 1
            resetBandOrder();
            updateOrderInputVisibility();
        });

        // 段时长步进：±500ms
        setRepeatListener(btnDurationMinus, () -> adjustValue(tvDurationValue, -500));
        setRepeatListener(btnDurationPlus, () -> adjustValue(tvDurationValue, +500));

        // 其他保持步进：脉宽 ±25µs、频率 ±1Hz、档位 ±1、时间 ±1 分钟
        setRepeatListener(btnPulseWidthMinus, () -> adjustValue(tvPulseWidthValue, -25));
        setRepeatListener(btnPulseWidthPlus, () -> adjustValue(tvPulseWidthValue, +25));
        setRepeatListener(btnFrequencyMinus, () -> adjustValue(tvFrequencyValue, -1));
        setRepeatListener(btnFrequencyPlus, () -> adjustValue(tvFrequencyValue, +1));
        setRepeatListener(btnGearMinus, () -> adjustValue(tvGearValue, -1));
        setRepeatListener(btnGearPlus, () -> adjustValue(tvGearValue, +1));
        setRepeatListener(btnTimeMinus, () -> adjustValue(tvTimeValue, -1));
        setRepeatListener(btnTimePlus, () -> adjustValue(tvTimeValue, +1));

        //  允许点数字直接输入（带范围校验）
        enableDirectInput(tvTotalSegmentsValue);
        enableDirectInput(tvDurationValue);
        enableDirectInput(tvPulseWidthValue);
        enableDirectInput(tvFrequencyValue);
        enableDirectInput(tvGearValue);
        enableDirectInput(tvTimeValue);

        // 设备切换监听
        deviceSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                // 保存当前设备的配置到对应的节点键
                if (currentDeviceIndex > 0) {
                    ProvisionedMeshNode prevNode = spinnerIndexToNodeMap.get(currentDeviceIndex);
                    if (prevNode != null) {
                        String prevKey = String.valueOf(prevNode.getUnicastAddress());
                        deviceSettings.put(prevKey, collectCurrentState());
                        Log.d(TAG, "保存设备配置到节点键: " + prevKey + " (索引: " + currentDeviceIndex + ")");
                    }
                }

                // 切换到新选择的设备
                currentDeviceIndex = position;
                Log.d(TAG, "切换到设备索引: " + position);

                // 加载新设备的配置，如果不存在则创建默认配置
                ParameterState state;
                if (position > 0) {
                    ProvisionedMeshNode node = spinnerIndexToNodeMap.get(position);
                    if (node != null) {
                        String nodeKey = String.valueOf(node.getUnicastAddress());
                        state = deviceSettings.get(nodeKey);
                        if (state == null) {
                            state = createDefaultState();
                            deviceSettings.put(nodeKey, state);
                            Log.d(TAG, "为节点键 " + nodeKey + " 创建默认配置");
                        } else {
                            Log.d(TAG, "加载节点键 " + nodeKey + " 的已保存配置");
                        }
                    } else {
                        state = createDefaultState();
                    }
                } else {
                    state = createDefaultState();
                }

                // 应用配置到UI界面
                applyState(state);

                // 如果选择的是具体设备（不是"选择设备"），显示对应的节点信息
                if (position > 0) {
                    ProvisionedMeshNode correspondingNode = spinnerIndexToNodeMap.get(position);
                    if (correspondingNode != null) {
                        Log.d(TAG, "当前选择的设备对应节点: " + correspondingNode.getNodeName() + " (地址: " + correspondingNode.getUnicastAddress() + ")");

                        // 首次点击颜色分配与发送逻辑已迁移到节点列表点击处理
                    }
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {
            }
        });

        // 播放/暂停/返回 主界面
        btnPlay.setOnClickListener(v -> {
            if (currentStatus == 0) {
                // 检查时间是否为0
                int totalTime = parseInt(tvTimeValue);
                if (totalTime <= 0) {
                    Toast.makeText(this, "请先设置时间", Toast.LENGTH_SHORT).show();
                    return; // 时间未设置时终止后续操作
                }

                // 将当前界面配置无条件保存到当前选择的设备，避免必须切换 Spinner 才更新
                int selectedSpinnerIndex = deviceSpinner.getSelectedItemPosition();
                if (selectedSpinnerIndex > 0) { // 0是"选择设备"，大于0才是实际节点
                    ProvisionedMeshNode selNode = spinnerIndexToNodeMap.get(selectedSpinnerIndex);
                    if (selNode != null) {
                        String nodeKey = String.valueOf(selNode.getUnicastAddress());
                        deviceSettings.put(nodeKey, collectCurrentState());
                        Log.d(TAG, "已保存当前界面配置至节点: " + nodeKey);
                    }
                }

                // 处理数据阻塞功能：对未选中的节点实施阻塞
                handleDataBlocking();

                // ▶ 从暂停切换到运行
                currentStatus = 1;
                btnPlay.setText("⏸"); // 按钮文字切换为暂停
                btnPlay.setSelected(true); // 设置按键为选中状态

                // 倒计时逻辑（如果还没开始）
                if (!isCounting && totalTime > 0) {
                    startCountdown();
                }

                // 开始会话日志记录
                sessionStartTime = new Date();
                voltage1History.clear();
                voltage2History.clear();
                currentHistory.clear();
                impedanceHistory.clear();
                Log.d(TAG, "会话开始，记录 startTime = " + sessionStartTime);

                // 开始数据采集
                isCollecting = true;
                handler.post(dataCollectorRunnable);

                // 测试验证设备与节点的映射关系
                testDeviceNodeMapping();

                // 依次向每个选中节点发送其专属波形参数
                sendWaveJsonToSelectedNodesSequentially(1);

            } else {
                // ⏸ 从运行切换到暂停
                currentStatus = 0;
                btnPlay.setText("▶");   // 按钮文字切换为开始
                btnPlay.setSelected(false); // 设置按键为未选中状态

                // 停止倒计时
                if (countDownTimer != null) {
                    countDownTimer.cancel();
                    countDownTimer = null;
                }

                // 停止采集
                isCollecting = false;

                // 发送停止命令（向选中节点）
                sendWaveJsonToSelectedNodesSequentially(0);

                // 保存日志 - 总是调用 endSessionAndSaveLog 来确保 totalTime 被正确保存
                endSessionAndSaveLog();
                isCounting = false; // 确保重置计数状态
                Log.d(TAG, "会话结束，日志已保存");
            }
        });
        //btnBluetoothMesh.setOnClickListener(v -> onBluetoothMeshPressed());

        // ✔ 显示悬浮数据界面
        LinearLayout popupOverlayPanel = findViewById(R.id.popupOverlayPanel);
        ImageButton btnClosePopup = findViewById(R.id.btnClosePopupOverlay);
        RecyclerView popupRecycler = findViewById(R.id.recycler_view_popup);

        btnReadData.setOnClickListener(v -> popupOverlayPanel.setVisibility(View.VISIBLE));
        btnClosePopup.setOnClickListener(v -> popupOverlayPanel.setVisibility(View.GONE));
        // 添加日志按钮点击事件
        @SuppressLint({"MissingInflatedId", "LocalSuppress"}) Button btnLog = findViewById(R.id.btnLog);
        btnLog.setOnClickListener(v -> {
            Intent intent = new Intent(CustomControlsActivity.this, LogRecordActivity.class);
            startActivity(intent);
        });

        // ✔ 设置 RecyclerView
        popupRecycler.setLayoutManager(new LinearLayoutManager(this));
        SentProfileAdapter adapter = new SentProfileAdapter(this);
        popupRecycler.setAdapter(adapter);

        viewModel = new ViewModelProvider(this).get(SentProfileViewModel.class);

        // ✔ LiveData 监听数据库
        viewModel.getAllSentProfilesLive().observe(this, profiles -> {
            if (profiles != null) {
                adapter.updateProfiles(profiles);
            }
        });

        // ✔ 点击列表项进行数据回填
        adapter.setOnItemClickListener(profiles -> {
            if (profiles != null && !profiles.isEmpty()) {
                // 设置总段数
                tvTotalSegmentsValue.setText(String.valueOf(profiles.size()));

                // 回填第一段的参数到当前界面
                SentProfile firstProfile = profiles.get(0);
                tvDurationValue.setText(firstProfile.segmentDuration + " ms");
                tvPulseWidthValue.setText(firstProfile.pulseWidth + " μs");
                tvFrequencyValue.setText(firstProfile.frequency + " Hz");

                // 保存配置名称
                currentConfigName = firstProfile.configName;

                ArrayAdapter<String> spinnerAdapter = (ArrayAdapter<String>) spinnerWaveformType.getAdapter();
                if (spinnerAdapter != null) {
                    int position = spinnerAdapter.getPosition(firstProfile.waveformType);
                    if (position >= 0) spinnerWaveformType.setSelection(position);
                }

                // 保存所有段参数到segmentParams
                segmentParams.clear();
                for (SentProfile profile : profiles) {
                    SegmentParams segmentParam = new SegmentParams();
                    segmentParam.durationMin = profile.segmentDuration;
                    segmentParam.pulseWidthMs = profile.pulseWidth;
                    segmentParam.frequencyHz = profile.frequency;
                    segmentParam.waveformType = spinnerAdapter.getPosition(profile.waveformType);
                    segmentParam.segmentRepeat = DEFAULT_SEGMENT_REPEAT;
                    segmentParam.waveformRepeat = DEFAULT_WAVEFORM_REPEAT;
                    segmentParams.put(profile.segmentIndex, segmentParam);
                }

                // 更新段选择器
                updateSegmentTabs();

                Toast.makeText(this, "已回填组配置（" + profiles.size() + "段）", Toast.LENGTH_SHORT).show();
            }
        });

        // ✔ 删除按钮逻辑
        adapter.setOnDeleteClickListener(profiles -> {
            if (profiles != null && !profiles.isEmpty()) {
                new AlertDialog.Builder(this)
                        .setTitle("确认删除")
                        .setMessage("是否删除此组配置（" + profiles.size() + "段）？")
                        .setPositiveButton("删除", (dialog, which) -> {
                            // 删除整个组
                            String groupId = profiles.get(0).groupId;
                            if (groupId != null) {
                                viewModel.deleteProfilesByGroupId(groupId);
                            }
                        })
                        .setNegativeButton("取消", null)
                        .show();
            }
        });

        // 初始逻辑（恢复持久化状态）
        int savedIndex = restoreStateIfAny();
        if (deviceSettings.isEmpty()) {
            resetBandOrder();
        }
        updateOrderInputVisibility();
        int maxIdx = 0;
        if (deviceSpinner.getAdapter() != null) {
            maxIdx = Math.max(0, deviceSpinner.getAdapter().getCount() - 1);
        }
        deviceSpinner.setSelection(Math.min(savedIndex, maxIdx));

        // 文本“应用”组合顺序
        btnApplyOrder.setOnClickListener(v -> {
            String s = etBandOrder.getText().toString().trim();
            int total = parseInt(tvTotalSegmentsValue);
            if (!s.matches("\\d{" + total + "}")) {
                Toast.makeText(this, "请输出" + total + "位数字的组合，例如 312", Toast.LENGTH_SHORT).show();
                return;
            }
            bandOrder.clear();
            for (char c : s.toCharArray()) {
                int seg = c - '0';
                if (seg < 1 || seg > total) {
                    Toast.makeText(this, "无效段号：" + seg, Toast.LENGTH_SHORT).show();
                    return;
                }
                bandOrder.add(seg);
            }
            drawCombinedWaveform();
        });

        // 保存
        btnSaveData.setOnClickListener(v -> {
            onSaveDataPressed();
            drawCombinedWaveform();
        });

        // 初始加载
        resetBandOrder();
        updateOrderInputVisibility();
        // 保持已恢复的设备选择，不再强制重置为索引0

        // —— 初始化 Headless 配网控制器：所有 UI 反馈都留在本页 ——
        headless = new MeshHeadlessProvisioner(
                this,
                this,
                new MeshHeadlessProvisioner.Callbacks() {
                    @Override public void onProvisionStart(ExtendedBluetoothDevice d) {
                        mProvisioningActive.set(true);
                        // 停止扫描、禁用点击、提示忙碌
                        mScannerVM.getScannerRepository().stopScanKeepResults();
                        setBusyUi(true, "正在配置 " + safeName(d));
                        // 重置最近进度状态
                        mLastProvisionerState = null;
                    }
                    @Override public void onProgressStateChanged(ProvisionerStates s) {
                        // 把状态显示在副标题上
                        setBusyUi(true, "状态: " + s.name());
                        // 记录最近进度状态
                        mLastProvisionerState = s;
                    }
                    @Override public void onRequireOobInput(@NonNull MeshHeadlessProvisioner.OobRequest req) {
                        showOobDialog(req);
                    }
                    @Override public void onFailed(@NonNull String reason) {
                        String lastState = (mLastProvisionerState != null) ? mLastProvisionerState.name() : "未知";
                        String connState = (mLastConnectionState != null) ? mLastConnectionState : "未知";
                        String msg = "配网失败: " + reason + " | 最后进度: " + lastState + " | 连接状态: " + connState;
                        Log.w(TAG, msg);
                        Toast.makeText(CustomControlsActivity.this, msg, Toast.LENGTH_LONG).show();
                        clearProvisioningLock(mLastProvisioningName, /*restartScan=*/true);
                    }
                    @Override public void onCompleted(@NonNull Intent resultIntent) {
                        clearProvisioningLock(mLastProvisioningName, /*restartScan=*/true);
                    }
                    @Override public void onLog(@NonNull String line) {
                        Log.d("HeadlessProv", line);
                    }
                }
        );
    }

    //生命周期里启动/停止扫描
    @Override
    protected void onStart() {
        super.onStart();
        // 注册系统广播（蓝牙/定位变化）
        mScannerVM.registerBroadcastReceivers();
        // 页面可见时立即尝试扫描
        maybeStartScan();
        // 启动在线检测脉冲
        if (onlineHelper != null) {
            onlineHelper.startTicker();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        mScannerVM.getScannerRepository().stopScan();
        mScannerVM.unregisterBroadcastReceivers();

        // 停止在线检测脉冲
        if (onlineHelper != null) {
            onlineHelper.stopTicker();
        }

        if (headless != null && headless.isRunning()) {
            headless.cancel();
        }

        mProvisioningActive.set(false);
        mCurrentProvisioningDevice.set(null);
        setBusyUi(false, null);
        // —— 取消所有待执行的可见性延迟任务，避免泄漏 ——
        mDelayHandler.removeCallbacksAndMessages(null);
        mPendingRunnables.clear();
        mPendingVisibility.clear();

        persistState();
    }

    /**
     * 自动初始化应用密钥
     * 当网络加载完成后，如果网络中没有应用密钥，则自动创建并添加默认应用密钥
     */
    private void autoInitializeAppKey() {
        try {
            MeshManagerApi meshManagerApi = mViewModel.getMeshManagerApi();
            if (meshManagerApi != null && meshManagerApi.getMeshNetwork() != null) {
                mMeshAutoConfigurator.initDefaultAppKey(meshManagerApi);
                Log.d(TAG, "自动初始化应用密钥完成");
            }
        } catch (Exception e) {
            Log.e(TAG, "自动初始化应用密钥失败: " + e.getMessage());
        }
    }

    private float average(List<Float> list) {
        if (list == null || list.isEmpty()) return 0f;
        float sum = 0;
        for (float v : list) sum += v;
        return sum / list.size();
    }

    private float max(List<Float> list) {
        if (list == null || list.isEmpty()) return 0f;
        float max = list.get(0);
        for (float v : list) if (v > max) max = v;
        return max;
    }

    private float min(List<Float> list) {
        if (list == null || list.isEmpty()) return 0f;
        float min = list.get(0);
        for (float v : list) if (v < min) min = v;
        return min;
    }

    private void endSessionAndSaveLog() {
        // 即使 sessionStartTime 为 null 也要保存记录，因为可能用户设置了时间但未开始倒计时
        Date endTime = new Date();
        long duration = 0;

        LogRecord record = new LogRecord();

        if (sessionStartTime != null) {
            duration = endTime.getTime() - sessionStartTime.getTime();
            record.startTime = sessionStartTime;
        } else {
            record.startTime = new Date(endTime.getTime() - remainingMs); // 使用剩余时间估算开始时间
        }

        record.endTime = endTime;
        record.duration = duration;

        // 用户设置参数（从 UI 获取）
        try {
            record.gear = Integer.parseInt(tvGearValue.getText().toString());
        } catch (Exception e) {
            record.gear = 0;
            Log.e(TAG, "解析 gear 失败", e);
        }

        try {
            record.totalTime = parseInt(tvTimeValue);
            Log.d(TAG, "获取到的 totalTime 值: " + record.totalTime);
        } catch (Exception e) {
            Log.e(TAG, "解析 totalTime 失败", e);
            record.totalTime = 0;
        }

        try {
            record.totalSegments = Integer.parseInt(tvTotalSegmentsValue.getText().toString());
        } catch (Exception e) {
            record.totalSegments = 0;
            Log.e(TAG, "解析 totalSegments 失败", e);
        }

        // 保存配置名称
        // 先设置当前已知的配置名称
        record.configName = currentConfigName;

        // 如果currentConfigName为空，则尝试使用上一次的配置名称
        if (currentConfigName == null || currentConfigName.isEmpty()) {
            // 尝试在后台线程中获取上一次保存的配置名称
            Executors.newSingleThreadExecutor().execute(() -> {
                SentProfile latestProfile = viewModel.getLatestProfile();
                if (latestProfile != null && latestProfile.configName != null && !latestProfile.configName.isEmpty()) {
                    final String configName = latestProfile.configName;
                    // 在UI线程中更新currentConfigName和record.configName
                    runOnUiThread(() -> {
                        currentConfigName = configName;
                        // 即使已经设置了record.configName，也再次设置以确保使用最新值
                        record.configName = currentConfigName;
                    });
                }
            });
        }

        // 新增：初始化段参数结构
        record.initSegmentParamsIfNeeded();

        // 新增：把主界面输入的每段参数写入 segmentParams
        Map<Integer, Map<String, Object>> segments = record.getSegmentParamsMap();
        for (int i = 1; i <= record.totalSegments; i++) {
            Map<String, Object> params = segments.get(i);
            if (params != null) {
                // 从已有的 segmentParams 中获取每段参数
                SegmentParams segmentParam = segmentParams.get(i);
                if (segmentParam != null) {
                    params.put("segmentDuration", segmentParam.durationMin); // 段时长
                    params.put("pulseWidth", segmentParam.pulseWidthMs);     // 脉宽
                    params.put("waveformType", segmentParam.waveformType);   // 波形类型
                    params.put("frequency", segmentParam.frequencyHz);       // 频率
                } else {
                    // 若无参数，使用默认值
                    params.put("segmentDuration", DEFAULT_DURATION_MS);
                    params.put("pulseWidth", DEFAULT_PULSE_WIDTH_US);
                    params.put("waveformType", DEFAULT_WAVEFORM_TYPE);
                    params.put("frequency", DEFAULT_FREQUENCY_HZ);
                }
            }
        }
        record.segmentParams = new Gson().toJson(segments);

        // 统计值
        record.avgVoltage1 = average(voltage1History);
        record.maxVoltage1 = max(voltage1History);
        record.minVoltage1 = min(voltage1History);

        record.avgVoltage2 = average(voltage2History);
        record.maxVoltage2 = max(voltage2History);
        record.minVoltage2 = min(voltage2History);

        record.avgCurrent = average(currentHistory);
        record.maxCurrent = max(currentHistory);
        record.minCurrent = min(currentHistory);

        // 使用ImpedanceDataFilter过滤无效的阻抗数据后计算统计值
        List<Float> validImpedanceData = ImpedanceDataFilter.filterValidImpedanceData(impedanceHistory);
        record.avgImpedance = ImpedanceDataFilter.calculateValidAverage(validImpedanceData);
        record.maxImpedance = ImpedanceDataFilter.calculateValidMax(validImpedanceData);
        record.minImpedance = ImpedanceDataFilter.calculateValidMin(validImpedanceData);

        // 保存历史曲线
        Gson gson = new Gson();
        record.voltage1History = gson.toJson(voltage1History);
        record.voltage2History = gson.toJson(voltage2History);
        record.currentHistory = gson.toJson(currentHistory);
        record.impedanceHistory = gson.toJson(impedanceHistory);

        // 存数据库
        Executors.newSingleThreadExecutor().execute(() -> {
            try {
                long id = AppDatabase.getInstance(this).logRecordDao().insertLogRecord(record);
                Log.d(TAG, "已保存 LogRecord 记录到数据库, totalTime: " + record.totalTime + ", ID: " + id);
            } catch (Exception e) {
                Log.e(TAG, "保存 LogRecord 失败", e);
            }
        });

        sessionStartTime = null; // 重置
    }

    /**
     * 给一个 Button 添加：单次点一下 + 长按自动重复回调
     */
    private void setRepeatListener(final Button button, final Runnable action) {
        final Handler handler = new Handler();
        final long initialDelay = 500;
        final long repeatDelay = 100;
        button.setOnTouchListener(new View.OnTouchListener() {
            private boolean isPressed = false;
            private final Runnable repeater = new Runnable() {
                @Override
                public void run() {
                    if (isPressed) {
                        action.run();
                        handler.postDelayed(this, repeatDelay);
                    }
                }
            };

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                switch (event.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        isPressed = true;
                        action.run();
                        handler.postDelayed(repeater, initialDelay);
                        return true;
                    case MotionEvent.ACTION_UP:
                    case MotionEvent.ACTION_CANCEL:
                        isPressed = false;
                        handler.removeCallbacks(repeater);
                        return true;
                }
                return false;
            }
        });
    }

    // ★ 允许在调节范围内自定义输入
    private void enableDirectInput(final TextView tv) {
        tv.setOnClickListener(v -> showNumberInputDialog(tv));
    }

    private void showNumberInputDialog(final TextView tv) {
        final int[] mm = getMinMaxFor(tv);
        final int min = mm[0];
        final int max = mm[1];
        final String suffix = getSuffixFor(tv);

        final EditText input = new EditText(this);
        input.setInputType(InputType.TYPE_CLASS_NUMBER);
        input.setText(String.valueOf(parseInt(tv)));
        input.setSelection(input.getText().length());
        String rangeEnd = (max == Integer.MAX_VALUE) ? "∞" : String.valueOf(max);
        input.setHint("范围: " + min + " - " + rangeEnd + (suffix.isEmpty() ? "" : " " + suffix));

        new AlertDialog.Builder(this)
                .setTitle("输入数值" + (suffix.isEmpty() ? "" : "（" + suffix + "）"))
                .setView(input)
                .setPositiveButton("确定", (d, w) -> {
                    String s = input.getText().toString().trim();
                    if (s.isEmpty()) {
                        Toast.makeText(this, "输入为空", Toast.LENGTH_SHORT).show();
                        return;
                    }
                    try {
                        int val = Integer.parseInt(s);
                        int clamped = Math.max(min, Math.min(val, max));
                        if (val != clamped) {
                            Toast.makeText(this, "超出范围，已调整为 " + clamped + (suffix.isEmpty() ? "" : suffix), Toast.LENGTH_SHORT).show();
                        }
                        tv.setText(clamped + suffix);

                        // 对时长、脉宽、频率的直接输入：保存并重绘
                        if (tv == tvDurationValue || tv == tvPulseWidthValue || tv == tvFrequencyValue) {
                            saveCurrentSegmentParams();
                            drawCombinedWaveform();
                        }

                        // 同步与副作用（与 adjustValue 中一致）
                        if (tv == tvTimeValue && !isCounting) {
                            remainingMs = clamped * 60_000L;
                            updateCountdownLabel();
                        }
                        if (tv == tvGearValue) {
                            tvGear.setText(String.valueOf(parseInt(tvGearValue)));

                        }
                        if (tv == tvTotalSegmentsValue) {
                            updateSegmentTabs();
                            loadCurrentSegmentParams();
                            updateOrderInputVisibility();
                            // 同步 bandOrder，并重绘组合波形
                            resetBandOrder();
                            drawCombinedWaveform();
                        }
                    } catch (NumberFormatException e) {
                        Toast.makeText(this, "无效数字", Toast.LENGTH_SHORT).show();
                    }
                })
                .setNegativeButton("取消", null)
                .show();
    }

    private int[] getMinMaxFor(final TextView tv) {
        int min = 0, max = Integer.MAX_VALUE;
        if (tv == tvTotalSegmentsValue) {
            min = TOTAL_SEG_MIN;
            max = TOTAL_SEG_MAX;
        } else if (tv == tvDurationValue) {
            min = 0; // 段时长允许 0 起
        } else if (tv == tvPulseWidthValue) {
            min = MIN_PULSE_WIDTH_US;
            max = MAX_PULSE_WIDTH_US;
        } else if (tv == tvFrequencyValue) {
            min = MIN_FREQUENCY_HZ;
            max = MAX_FREQUENCY_HZ;
        } else if (tv == tvGearValue) {
            min = GEAR_MIN;
            max = GEAR_MAX;
        } else if (tv == tvTimeValue) {
            min = 0; // 分钟允许 0 起
        }
        return new int[]{min, max};
    }

    private String getSuffixFor(final TextView tv) {
        if (tv == tvDurationValue) return "ms";
        if (tv == tvPulseWidthValue) return "µs";
        if (tv == tvFrequencyValue) return "Hz";
        if (tv == tvTimeValue) return "min";
        return "";
    }

    private void initDeviceSpinner() {
        // 初始化设备列表，只显示"选择设备"提示
        deviceSpinnerItems.clear();
        deviceSpinnerItems.add("选择设备");

        // 使用带颜色显示的自定义适配器
        deviceSpinnerAdapter = new ColoredDeviceAdapter(
                android.R.layout.simple_spinner_item,
                deviceSpinnerItems
        );
        deviceSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        deviceSpinner.setAdapter(deviceSpinnerAdapter);

        // 已移除触摸补偿逻辑，颜色分配改为节点列表点击触发
    }

    private void initWaveformTypeSpinner() {
        ArrayAdapter<String> adapter = new ArrayAdapter<>(
                this, android.R.layout.simple_spinner_item,
                new String[]{
                        "正向正弦波", "负向正弦波", "双向正弦波",
                        "正向三角波", "负向三角波", "双向三角波",
                        "正向脉冲波", "负向脉冲波", "双向脉冲波"
                }
        );
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spinnerWaveformType.setAdapter(adapter);

        // 设置波形类型切换时保存并重绘组合波形
        spinnerWaveformType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                // 选择波形类型后，保存当前段参数并重绘组合波形
                saveCurrentSegmentParams();
                drawCombinedWaveform();
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {
            }
        });

        // 可选：设置初始透明度为1，避免动画冲突
        imgOutputWaveform.setAlpha(1f);
    }


    private ParameterState createDefaultState() {
        ParameterState s = new ParameterState();
        s.totalSegments = DEFAULT_TOTAL_SEGMENTS;
        s.gear = DEFAULT_GEAR;
        s.timeMin = DEFAULT_TIME_MIN;
        s.remainingMs = DEFAULT_TIME_MIN * 60_000L;
        s.segmentParams = new HashMap<>();
        s.selectedSegment = 1;
        s.bandOrder = new ArrayList<>();
        for (int i = 1; i <= DEFAULT_TOTAL_SEGMENTS; i++) s.bandOrder.add(i);

        SegmentParams p = new SegmentParams();
        p.segmentRepeat = DEFAULT_SEGMENT_REPEAT;   // 保留为 0
        p.durationMin = DEFAULT_DURATION_MS;
        p.waveformType = DEFAULT_WAVEFORM_TYPE;
        p.pulseWidthMs = DEFAULT_PULSE_WIDTH_US;
        p.frequencyHz = DEFAULT_FREQUENCY_HZ;
        p.waveformRepeat = DEFAULT_WAVEFORM_REPEAT;  // 保留为 0
        s.segmentParams.put(1, p);
        s.stimMode = STIM_MODE_TP;   // 默认经皮

        return s;
    }

    private ParameterState collectCurrentState() {
        // 先保存当前选中段的参数
        saveCurrentSegmentParams();

        // 确保段参数与顺序完整覆盖 1..总段数
        int total = parseInt(tvTotalSegmentsValue);
        if (selectedSegmentIndex < 1) selectedSegmentIndex = 1;
        if (selectedSegmentIndex > total) selectedSegmentIndex = total;
        if (bandOrder == null) bandOrder = new ArrayList<>();

        boolean[] present = new boolean[total + 1];
        List<Integer> cleanedOrder = new ArrayList<>();
        for (Integer seg : bandOrder) {
            if (seg != null && seg >= 1 && seg <= total && !present[seg]) {
                cleanedOrder.add(seg);
                present[seg] = true;
            }
        }
        for (int i = 1; i <= total; i++) {
            if (!present[i]) cleanedOrder.add(i);
            if (!segmentParams.containsKey(i)) {
                segmentParams.put(i, makeDefaultParams());
            }
        }
        bandOrder.clear();
        bandOrder.addAll(cleanedOrder);

        ParameterState s = new ParameterState();
        s.totalSegments = total;
        s.gear = parseInt(tvGearValue);
        s.timeMin = parseInt(tvTimeValue);
        s.remainingMs = remainingMs;
        s.segmentParams = deepCopySegmentParams(segmentParams);
        s.selectedSegment = selectedSegmentIndex;
        s.bandOrder = new ArrayList<>(bandOrder);
        s.stimMode = this.stimMode;   // ★ 新增
        return s;
    }

    // 深拷贝段参数，避免跨设备共享引用导致参数同步
    private Map<Integer, SegmentParams> deepCopySegmentParams(Map<Integer, SegmentParams> src) {
        Map<Integer, SegmentParams> copy = new HashMap<>();
        if (src != null) {
            for (Map.Entry<Integer, SegmentParams> e : src.entrySet()) {
                SegmentParams o = e.getValue();
                if (o != null) {
                    SegmentParams p = new SegmentParams();
                    p.segmentRepeat = o.segmentRepeat;
                    p.durationMin = o.durationMin;
                    p.waveformType = o.waveformType;
                    p.pulseWidthMs = o.pulseWidthMs;
                    p.frequencyHz = o.frequencyHz;
                    p.waveformRepeat = o.waveformRepeat;
                    copy.put(e.getKey(), p);
                } else {
                    copy.put(e.getKey(), null);
                }
            }
        }
        return copy;
    }

    // 持久化当前所有设备设置与当前设备索引
    private void persistState() {
        try {
            if (currentDeviceIndex > 0) {
                ProvisionedMeshNode node = spinnerIndexToNodeMap.get(currentDeviceIndex);
                if (node != null) {
                    String nodeKey = String.valueOf(node.getUnicastAddress());
                    deviceSettings.put(nodeKey, collectCurrentState());
                }
            }
            SharedPreferences sp = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
            Gson gson = new Gson();
            String json = gson.toJson(deviceSettings);
            List<Integer> addrList = new ArrayList<>();
            for (ProvisionedMeshNode n : selectedNodes) {
                if (n != null) addrList.add(n.getUnicastAddress());
            }
            String addrJson = gson.toJson(addrList);
            String colorJson = gson.toJson(firstClickAssignedColor);
            sp.edit()
                    .putString(KEY_DEVICE_SETTINGS, json)
                    .putInt(KEY_CURRENT_DEVICE_INDEX, currentDeviceIndex < 0 ? 0 : currentDeviceIndex)
                    .putString(KEY_SELECTED_NODE_ADDRS, addrJson)
                    .putString(KEY_ASSIGNED_COLORS, colorJson)
                    .putInt(KEY_NEXT_COLOR_TO_ASSIGN, nextColorToAssign)
                    .apply();
            Log.d(TAG, "状态已持久化: index=" + currentDeviceIndex + ", settings=" + deviceSettings.size());
        } catch (Exception e) {
            Log.e(TAG, "持久化失败", e);
        }
    }

    // 恢复持久化状态；返回应选中的设备索引
    private int restoreStateIfAny() {
        SharedPreferences sp = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
        String json = sp.getString(KEY_DEVICE_SETTINGS, null);
        int savedIndex = sp.getInt(KEY_CURRENT_DEVICE_INDEX, 0);
        if (json != null && !json.isEmpty()) {
            try {
                Type strType = new TypeToken<Map<String, ParameterState>>() {}.getType();
                Map<String, ParameterState> strMap = new Gson().fromJson(json, strType);
                if (strMap != null && !strMap.isEmpty()) {
                    deviceSettings.clear();
                    deviceSettings.putAll(strMap);
                    Log.d(TAG, "已恢复持久化状态(字符串键): deviceSettings=" + deviceSettings.size());
                } else {
                    Type intType = new TypeToken<Map<Integer, ParameterState>>() {}.getType();
                    Map<Integer, ParameterState> intMap = new Gson().fromJson(json, intType);
                    if (intMap != null && !intMap.isEmpty()) {
                        legacyIndexSettingsPending.clear();
                        legacyIndexSettingsPending.putAll(intMap);
                        Log.d(TAG, "检测到旧索引键配置，等待迁移: legacy=" + legacyIndexSettingsPending.size());
                    }
                }
            } catch (Exception e) {
                Log.e(TAG, "恢复持久化状态失败", e);
            }
        }
        try {
            Gson gson = new Gson();
            String addrJson = sp.getString(KEY_SELECTED_NODE_ADDRS, null);
            if (addrJson != null && !addrJson.isEmpty()) {
                Type t = new TypeToken<List<Integer>>() {}.getType();
                List<Integer> addrs = gson.fromJson(addrJson, t);
                selectedNodes.clear();
                List<ProvisionedMeshNode> allNodes = (mViewModel != null && mViewModel.getNodes() != null) ? mViewModel.getNodes().getValue() : null;
                if ((allNodes == null || allNodes.isEmpty()) && mViewModel != null && mViewModel.getNetworkLiveData().getMeshNetwork() != null) {
                    allNodes = mViewModel.getNetworkLiveData().getMeshNetwork().getNodes();
                }
                if (allNodes != null) {
                    for (Integer addr : addrs) {
                        for (ProvisionedMeshNode n : allNodes) {
                            if (n != null && n.getUnicastAddress() == addr) {
                                selectedNodes.add(n);
                                break;
                            }
                        }
                    }
                }
            }
            String colorJson = sp.getString(KEY_ASSIGNED_COLORS, null);
            if (colorJson != null && !colorJson.isEmpty()) {
                Type mt = new TypeToken<Map<String, Integer>>() {}.getType();
                Map<String, Integer> m = gson.fromJson(colorJson, mt);
                firstClickAssignedColor.clear();
                if (m != null) firstClickAssignedColor.putAll(m);
                for (Map.Entry<String, Integer> e : new HashMap<>(firstClickAssignedColor).entrySet()) {
                    if (e.getValue() != null && e.getValue() == 3) {
                        firstClickAssignedColor.put(e.getKey(), 1);
                    }
                }
            }
            nextColorToAssign = sp.getInt(KEY_NEXT_COLOR_TO_ASSIGN, nextColorToAssign);
            if (nextColorToAssign == 3) nextColorToAssign = 4;
            updateDeviceSpinner();
            if (mNodeAdapter != null) {
                mNodeAdapter.setAssignedColors(firstClickAssignedColor);
            }
            if (deviceSpinnerAdapter != null) deviceSpinnerAdapter.notifyDataSetChanged();
            updateNodeAdapterSelection();
        } catch (Exception e) {
            Log.e(TAG, "恢复选择/颜色失败", e);
        }
        return Math.max(0, savedIndex);
    }

    private void applyState(ParameterState s) {
        if (countDownTimer != null) {
            countDownTimer.cancel();
            countDownTimer = null;
        }
        isCounting = false;
        btnPlay.setText("▶");
        btnPlay.setSelected(false); // 设置按键为未选中状态

        // 夹取核心字段，避免越界
        int total = Math.max(TOTAL_SEG_MIN, Math.min(s.totalSegments, TOTAL_SEG_MAX));
        int gear = Math.max(GEAR_MIN, Math.min(s.gear, GEAR_MAX));
        int timeMin = Math.max(0, s.timeMin);
        long rem = Math.max(0L, s.remainingMs);

        tvTotalSegmentsValue.setText(String.valueOf(total));
        tvGearValue.setText(String.valueOf(gear));
        tvTimeValue.setText(String.valueOf(timeMin));
        remainingMs = rem;
        updateCountdownLabel();
        // —— 恢复刺激方式 ——
        stimMode = (s.stimMode == STIM_MODE_ND) ? STIM_MODE_ND : STIM_MODE_TP;

        if (tgStimMode != null) {
            tgStimMode.check(stimMode == STIM_MODE_ND ? R.id.btnNeedle : R.id.btnTranscutaneous);
        } else {
            // 如果你用的是 RadioGroup，则改为：
            // RadioGroup rg = findViewById(R.id.rgStimMode);
            // if (rg != null) rg.check(stimMode == STIM_MODE_ND ? R.id.rbNeedle : R.id.rbTranscutaneous);
        }


        // 同步左侧“档位”显示
        tvGear.setText(String.valueOf(gear));

        segmentParams = (s.segmentParams != null) ? deepCopySegmentParams(s.segmentParams) : new HashMap<>();
        selectedSegmentIndex = Math.max(1, Math.min(s.selectedSegment, total));

        // 清洗 bandOrder：仅保留 1..total 范围；为空则回退为 1..total
        List<Integer> cleaned = new ArrayList<>();
        if (s.bandOrder != null) {
            for (int seg : s.bandOrder) {
                if (seg >= 1 && seg <= total) cleaned.add(seg);
            }
        }
        if (cleaned.isEmpty()) {
            for (int i = 1; i <= total; i++) cleaned.add(i);
        }
        bandOrder = cleaned;

        updateSegmentTabs();
        loadCurrentSegmentParams();
        updateOrderInputVisibility();



        if (orderInputContainer.getVisibility() == View.VISIBLE) {
            StringBuilder sb = new StringBuilder();
            for (int seg : bandOrder) sb.append(seg);
            etBandOrder.setText(sb.toString());
        }
    }

    private void resetBandOrder() {
        bandOrder.clear();
        int total = parseInt(tvTotalSegmentsValue);
        for (int i = 1; i <= total; i++) bandOrder.add(i);
    }

    private void updateOrderInputVisibility() {
        int total = parseInt(tvTotalSegmentsValue);
        if (total <= 1) {
            orderInputContainer.setVisibility(View.GONE);
            bandOrder.clear();
            bandOrder.add(1);
            etBandOrder.setText("1");
            drawCombinedWaveform();
        } else {
            orderInputContainer.setVisibility(View.VISIBLE);
        }
    }

    private void drawCombinedWaveform() {
        int segmentCount = bandOrder.size(); // 1~3
        if (segmentCount < 1 || segmentCount > 3) return;

        // 获取容器大小
        int viewWidth = imgOutputWaveform.getWidth();
        int viewHeight = imgOutputWaveform.getHeight();
        if (viewWidth == 0 || viewHeight == 0) {
            // 等 UI 完全绘制完后再执行（异步）
            imgOutputWaveform.post(this::drawCombinedWaveform);
            return;
        }

        // 创建透明背景合成图
        Bitmap combinedBitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(combinedBitmap);
        canvas.drawColor(Color.WHITE);
        drawGridAndAxes(canvas, viewWidth, viewHeight);

        //  设置画笔
        Paint paint = new Paint();
        paint.setColor(Color.WHITE);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(3f);
        paint.setAntiAlias(true);

        //  遍历每段，绘制波形
        for (int i = 0; i < segmentCount; i++) {
            int segment = bandOrder.get(i);
            SegmentParams p = segmentParams.get(segment);
            if (p == null) continue;

            int waveformType = p.waveformType;
            int repeatCount = Math.max(1, p.segmentRepeat);
            int durationMs = Math.max(100, p.durationMin);
            int pulseWidthUs = Math.max(100, p.pulseWidthMs);
            int frequencyHz = Math.max(1, p.frequencyHz);

            int segmentWidth = viewWidth / segmentCount;

            // 颜色设置
            paint.setColor(colorForSegment(i));

            // 基于单周期平铺，消除段接缝造成的短间隔
            boolean biphasic = (waveformType == 2 || waveformType == 5 || waveformType == 8);
            float periodMs = 1000f / Math.max(1f, frequencyHz);
            float pulseMsF = pulseWidthUs / 1000f;
            float halfGapMs = Math.max(0f, (periodMs - 2f * pulseMsF) / 2f);
            float gapAfterPulseMs = Math.max(0f, periodMs - pulseMsF);
            float pulseExpandFactor = 40f;
            float gapCompressFactor = 6f;

            // 单周期未缩放单位（非线性映射后）
            float unscaledCycleUnits = biphasic
                    ? pulseMsF * pulseExpandFactor
                    + halfGapMs / gapCompressFactor
                    + pulseMsF * pulseExpandFactor
                    + halfGapMs / gapCompressFactor
                    : pulseMsF * pulseExpandFactor
                    + gapAfterPulseMs / gapCompressFactor;

            // 段内平均分配周期（强制整数周期），得到单周期像素宽与缩放因子
            int cyclesCount = Math.max(1, Math.round(durationMs / Math.max(1f, periodMs)));
            float cycleWidthPx = Math.max(1f, (float) segmentWidth / cyclesCount);
            float norm = cycleWidthPx / Math.max(1f, unscaledCycleUnits);

            // 构建“单周期”路径（直接用像素坐标绘制）
            Path path = new Path();
            float cursorX = 0f;
            float centerY = viewHeight / 2f;
            float amplitude = viewHeight / 2f * 0.8f;

            if (biphasic) {
                float px1 = pulseMsF * pulseExpandFactor * norm;
                switch (waveformType) {
                    case 2: drawSinePulse(path, cursorX, px1, centerY,  amplitude, 1f); break;
                    case 5: drawTrianglePulse(path, cursorX, px1, centerY,  amplitude, 1f); break;
                    case 8: drawRectPulse(path, cursorX, px1, centerY,  amplitude, 1f); break;
                }
                cursorX += px1;
                float pxGap = halfGapMs / gapCompressFactor * norm;
                cursorX += pxGap;

                float px2 = pulseMsF * pulseExpandFactor * norm;
                switch (waveformType) {
                    case 2: drawSinePulse(path, cursorX, px2, centerY, -amplitude, 1f); break;
                    case 5: drawTrianglePulse(path, cursorX, px2, centerY, -amplitude, 1f); break;
                    case 8: drawRectPulse(path, cursorX, px2, centerY, -amplitude, 1f); break;
                }
                cursorX += px2;
                pxGap = pxGap;
                cursorX += pxGap;
            } else {
                float pxPulse = pulseMsF * pulseExpandFactor * norm;
                switch (waveformType) {
                    case 0: drawSinePulse(path, cursorX, pxPulse, centerY,  amplitude, 1f); break;
                    case 1: drawSinePulse(path, cursorX, pxPulse, centerY, -amplitude, 1f); break;
                    case 3: drawTrianglePulse(path, cursorX, pxPulse, centerY,  amplitude, 1f); break;
                    case 4: drawTrianglePulse(path, cursorX, pxPulse, centerY, -amplitude, 1f); break;
                    case 6: drawRectPulse(path, cursorX, pxPulse, centerY,  amplitude, 1f); break;
                    case 7: drawRectPulse(path, cursorX, pxPulse, centerY, -amplitude, 1f); break;
                }
                cursorX += pxPulse;
                float pxGap = gapAfterPulseMs / gapCompressFactor * norm;
                cursorX += pxGap;
            }

            // 用“单周期宽度”平铺，接缝总在周期边界
            canvas.save();
            final float segLeft = i * segmentWidth;
            final float segRight = (i + 1) * segmentWidth;
            canvas.clipRect(segLeft, 0, segRight, viewHeight);

            final float tileWidth = Math.max(1f, cursorX);
            final float scroll = (waveScrollOffsetPx % tileWidth);
            final float firstTx = segLeft + scroll - tileWidth;
            for (float tx = firstTx; tx < segRight + tileWidth; tx += tileWidth) {
                canvas.save();
                canvas.translate(tx, 0);
                canvas.drawPath(path, paint);
                canvas.restore();
            }
            canvas.restore();

            // 画横坐标刻度（按非线性映射后的位置显示真实时间）
            {
                Paint axisPaint = new Paint();
                axisPaint.setColor(Color.LTGRAY);
                axisPaint.setStrokeWidth(1.0f);
                axisPaint.setAntiAlias(true);

                Paint tickPaint = new Paint();
                tickPaint.setColor(Color.DKGRAY);
                tickPaint.setStrokeWidth(2.4f); // 刻度略粗，便于在轴线上显眼
                tickPaint.setAntiAlias(true);

                // 底部基准线（X轴）与重合刻度：刻度绘制为沿轴的短横线
                float baseY = viewHeight - 2f;
                canvas.drawLine(i * segmentWidth, baseY, (i + 1) * segmentWidth, baseY, axisPaint);

                float majorStep = Math.max(periodMs, durationMs / 10f);
                float tickLen = 10f; // 竖向刻度长度（与横轴重叠并向上延伸）

                Paint xLabelPaint = new Paint();
                xLabelPaint.setColor(Color.BLACK);
                xLabelPaint.setTextSize(14f);
                xLabelPaint.setAntiAlias(true);
                xLabelPaint.setTextAlign(Paint.Align.CENTER);

                for (float t = 0f; t <= durationMs + 0.0001f; t += majorStep) {
                    // 计算 t 经非线性映射后的 X 像素位置
                    float acc = 0f;
                    float consumed = 0f;
                    while (consumed < t) {
                        if (biphasic) {
                            float ms = Math.min(pulseMsF, t - consumed);
                            acc += ms * pulseExpandFactor; consumed += ms;
                            if (consumed >= t) break;
                            ms = Math.min(halfGapMs, t - consumed);
                            acc += ms / gapCompressFactor; consumed += ms;
                            if (consumed >= t) break;
                            ms = Math.min(pulseMsF, t - consumed);
                            acc += ms * pulseExpandFactor; consumed += ms;
                            if (consumed >= t) break;
                            ms = Math.min(halfGapMs, t - consumed);
                            acc += ms / gapCompressFactor; consumed += ms;
                        } else {
                            float ms = Math.min(pulseMsF, t - consumed);
                            acc += ms * pulseExpandFactor; consumed += ms;
                            if (consumed >= t) break;
                            ms = Math.min(gapAfterPulseMs, t - consumed);
                            acc += ms / gapCompressFactor; consumed += ms;
                        }
                    }
                    float x = i * segmentWidth + acc * norm;
                    if (x > i * segmentWidth + segmentWidth + 1f) break;

                    // 竖向刻度：与横轴重叠（从轴线上向上画）
                    canvas.drawLine(x, baseY - tickLen, x, baseY, tickPaint);

                    // 刻度单位与标签：在轴线上方居中显示（单位 ms）
                    String label = String.format(java.util.Locale.getDefault(), "%.0f ms", t);
                    float textY = baseY - tickLen - 2f;
                    // 为避免文字越界，必要时限制到当前分段范围
                    float xMin = i * segmentWidth + 4f;
                    float xMax = (i + 1) * segmentWidth - 4f;
                    float tx = Math.max(xMin, Math.min(xMax, x));
                    canvas.drawText(label, tx, textY, xLabelPaint);
                }

                // 纵坐标轴（Y轴）与刻度
                float yAxisX = i * segmentWidth + 2f;
                canvas.drawLine(yAxisX, 0f, yAxisX, viewHeight, tickPaint);

                Paint axisLabelPaint = new Paint();
                axisLabelPaint.setColor(Color.BLACK);
                axisLabelPaint.setTextSize(20f);
                axisLabelPaint.setAntiAlias(true);

                float[] yTicks = {0f, viewHeight * 0.25f, viewHeight * 0.5f, viewHeight * 0.75f, viewHeight};
                String[] yLabels = {"100%", "75%", "50%", "25%", "0%"};
                for (int k = 0; k < yTicks.length; k++) {
                    float y = yTicks[k];
                    canvas.drawLine(yAxisX, y, yAxisX + 12f, y, tickPaint);
                    String yLbl = yLabels[k];
                    if (yLbl != null) {
                        canvas.drawText(yLbl, yAxisX + 16f, Math.max(15f, y - 4f), axisLabelPaint);
                    }
                }
            }

            // 画注释
            drawSegmentInfo(canvas, i * segmentWidth, viewHeight, segment, p);
        }

        // 设置合成图
        imgOutputWaveform.setImageBitmap(combinedBitmap);
    }

    private int colorForSegment(int index) {
        int[] colors = {
                Color.parseColor("#FFC0CB"), // 粉
                Color.parseColor("#4CAF50"), // 绿
                Color.parseColor("#FF9800")  // 橙
        };
        return colors[index % colors.length];
    }

    private void drawGridAndAxes(Canvas canvas, int width, int height) {
        Paint gridPaint = new Paint();
        gridPaint.setColor(Color.LTGRAY);
        gridPaint.setStrokeWidth(1f);
        gridPaint.setStyle(Paint.Style.STROKE);

        int stepY = height / 8;
        for (int y = 0; y <= height; y += stepY) {
            canvas.drawLine(0, y, width, y, gridPaint);
        }

        Paint axisPaint = new Paint();
        axisPaint.setColor(Color.BLACK);
        axisPaint.setStrokeWidth(2f);

        // 中心横线（0线）
        canvas.drawLine(0, height / 2f, width, height / 2f, axisPaint);
    }

    private void drawSegmentInfo(Canvas canvas, float startX, int viewHeight, int segmentIndex, SegmentParams p) {
        Paint textPaint = new Paint();
        textPaint.setColor(Color.BLACK);
        textPaint.setTextSize(26f);
        textPaint.setAntiAlias(true);

        // 添加完整信息：段号、波形类型、频率、脉宽、持续时间
        String label = "段 " + (segmentIndex + 1)
                + "｜" + getWaveformName(p.waveformType)
                + "｜" + p.frequencyHz + "Hz"
                + "｜" + p.pulseWidthMs + "µs"
                + "｜" + p.durationMin + "ms";

        canvas.drawText(label, startX + 10, viewHeight - 10, textPaint);
    }

    private String getWaveformName(int waveformType) {
        return switch (waveformType) {
            case 1 -> "正向正弦波";
            case 4 -> "负向正弦波";
            case 7 -> "双向正弦波";
            case 2 -> "正向三角波";
            case 5 -> "负向三角波";
            case 8 -> "双向三角波";
            case 3 -> "正向矩形波";
            case 6 -> "负向矩形波";
            case 9 -> "双向矩形波";
            default -> "未知波形";
        };
    }

    private void saveCurrentSegmentParams() {  //保存数据的逻辑
        // 不再从已移除的 UI 读取 segmentRepeat / waveformRepeat，保留原值
        SegmentParams p = segmentParams.get(selectedSegmentIndex);
        if (p == null) {
            p = new SegmentParams();
            p.segmentRepeat = DEFAULT_SEGMENT_REPEAT;
            p.waveformRepeat = DEFAULT_WAVEFORM_REPEAT;
        }
        p.durationMin = parseInt(tvDurationValue);
        p.waveformType = spinnerWaveformType.getSelectedItemPosition();
        p.pulseWidthMs = parseInt(tvPulseWidthValue);
        p.frequencyHz = parseInt(tvFrequencyValue);
        segmentParams.put(selectedSegmentIndex, p);
    }

    private void deleteCurrentSegmentParams() {  // 删除段参数
        int selectedIndex = selectedSegmentIndex;
        int total = parseInt(tvTotalSegmentsValue);
        SegmentParams p = segmentParams.get(selectedSegmentIndex);

        if(total > 1){
            StringBuilder message = new StringBuilder();
            message.append("确定要删除第 ").append(selectedIndex).append(" 段吗？\n");
            message.append("当前段数的参数：\n");

            message.append(String.format("%-6s : %-6s    %-6s : %-6s\n",
                    "段时长", parseInt(tvDurationValue) + " ms",
                    "脉宽", parseInt(tvPulseWidthValue) + " µs"));
            message.append(String.format("%-6s : %-6s    %-6s : %-6s\n",
                    "波形种类", getWaveformName(spinnerWaveformType.getSelectedItemPosition()),
                    "频率", parseInt(tvFrequencyValue) + " Hz"));

            new AlertDialog.Builder(this)
                    .setTitle("删除确认")
                    .setMessage(message.toString())
                    .setPositiveButton("确定", (dialog, which) -> {
                        if (selectedIndex == total) {
                            segmentParams.put(selectedIndex, makeDefaultParams());
                        } else {
                            for (int j = selectedIndex; j < total; j++) {
                                SegmentParams next = segmentParams.get(j + 1);
                                if (next != null) {
                                    segmentParams.put(j, next);
                                } else {
                                    segmentParams.put(j, makeDefaultParams());
                                }
                            }
                            segmentParams.put(total, makeDefaultParams());
                        }
                        adjustValue(tvTotalSegmentsValue, -1);
                    })
                    .setNegativeButton("取消", (dialog, which) -> {
                        dialog.dismiss();
                    })
                    .show();
        }
    }

    private SegmentParams makeDefaultParams() {
        SegmentParams p = new SegmentParams();
        p.segmentRepeat = DEFAULT_SEGMENT_REPEAT;
        p.durationMin = DEFAULT_DURATION_MS;
        p.waveformType = DEFAULT_WAVEFORM_TYPE;
        p.pulseWidthMs = DEFAULT_PULSE_WIDTH_US;
        p.frequencyHz = DEFAULT_FREQUENCY_HZ;
        p.waveformRepeat = DEFAULT_WAVEFORM_REPEAT;
        return p;
    }

    private void loadCurrentSegmentParams() {
        SegmentParams p = segmentParams.get(selectedSegmentIndex);
        if (p == null) {
            p = new SegmentParams();
            p.segmentRepeat = DEFAULT_SEGMENT_REPEAT;
            p.durationMin = DEFAULT_DURATION_MS;
            p.waveformType = DEFAULT_WAVEFORM_TYPE;
            p.pulseWidthMs = DEFAULT_PULSE_WIDTH_US;
            p.frequencyHz = DEFAULT_FREQUENCY_HZ;
            p.waveformRepeat = DEFAULT_WAVEFORM_REPEAT;
            segmentParams.put(selectedSegmentIndex, p);
        }
        tvDurationValue.setText(p.durationMin + "ms");
        spinnerWaveformType.setSelection(p.waveformType);
        tvPulseWidthValue.setText(p.pulseWidthMs + "µs");
        tvFrequencyValue.setText(p.frequencyHz + "Hz");
    }

    private void updateSegmentTabs() {
        segmentTabsContainer.removeAllViews();
        int total = parseInt(tvTotalSegmentsValue);
        selectedSegmentIndex = Math.max(1, Math.min(selectedSegmentIndex, total));
        for (int i = 1; i <= total; i++) {
            Button tab = new Button(this);
            tab.setText(circledNumber(i));
            tab.setTag(i);
            tab.setAlpha(i == selectedSegmentIndex ? 1f : 0.5f);
            tab.setOnClickListener(v -> {
                saveCurrentSegmentParams();
                selectedSegmentIndex = (int) v.getTag();
                updateSegmentTabs();
                loadCurrentSegmentParams();
                // 切换段后重绘组合波形
                drawCombinedWaveform();
            });
            segmentTabsContainer.addView(tab);
        }
    }

    private String circledNumber(int n) {
        final String[] circles = {
                "①", "②", "③", "④", "⑤", "⑥", "⑦", "⑧", "⑨", "⑩",
                "⑪", "⑫", "⑬", "⑭", "⑮", "⑯", "⑰", "⑱", "⑲", "⑳"
        };
        return (n >= 1 && n <= circles.length) ? circles[n - 1] : String.valueOf(n);
    }

    private void adjustValue(final TextView tv, final int delta) {
        int val = parseInt(tv) + delta;
        int min = 0, max = Integer.MAX_VALUE;
        String suffix = "";

        if (tv == tvTotalSegmentsValue) {
            // 总段数：1..3
            min = TOTAL_SEG_MIN;
            max = TOTAL_SEG_MAX;
        } else if (tv == tvDurationValue) {
            // 段时长：步进由调用处控制（±500ms），这里仅保证非负
            suffix = "ms";
            min = 0;
        } else if (tv == tvPulseWidthValue) {
            // 脉宽：100..1000 µs
            suffix = "µs";
            min = MIN_PULSE_WIDTH_US;
            max = MAX_PULSE_WIDTH_US;
        } else if (tv == tvFrequencyValue) {
            // 频率：1..120 Hz
            suffix = "Hz";
            min = MIN_FREQUENCY_HZ;
            max = MAX_FREQUENCY_HZ;
        } else if (tv == tvGearValue) {
            // 档位：1..8
            min = GEAR_MIN;
            max = GEAR_MAX;
        } else if (tv == tvTimeValue) {
            // 时间：保持分钟单位，允许从 0 开始
            suffix = "min";
            min = 0;
        }

        val = Math.max(min, Math.min(val, max));
        tv.setText(val + suffix);

        // 对时长、脉宽、频率的修改：保存并重绘
        if (tv == tvDurationValue || tv == tvPulseWidthValue || tv == tvFrequencyValue) {
            saveCurrentSegmentParams();
            drawCombinedWaveform();
        }

        if (tv == tvTimeValue && !isCounting) {
            remainingMs = val * 60_000L;
            updateCountdownLabel();
        }
        if (tv == tvGearValue) {
            tvGear.setText(String.valueOf(parseInt(tvGearValue)));

        }
        if (tv == tvTotalSegmentsValue) {
            updateSegmentTabs();
            loadCurrentSegmentParams();  //加载当前段参数
            updateOrderInputVisibility();  //更新段顺序输入框可见性
            // 总段数变化后重绘组合波形
            drawCombinedWaveform();
        }
    }

    private int parseInt(TextView tv) {
        String num = tv.getText().toString().replaceAll("[^0-9]", "");
        return num.isEmpty() ? 0 : Integer.parseInt(num);
    }

    private void updateCountdownLabel() {
        long s = remainingMs / 1000;
        tvOnOff.setText(String.format(" %02d:%02d", s / 60, s % 60));
    }

    // ======== ★ 电流映射 & 发送 ========

    // 设备整数单位使用 0.1 mA（若固件要 μA，把这行换成 mAToMicroAmp）
    private int mAToDeviceDeci(float mA) { return Math.round(mA * 10f); }
// private int mAToMicroAmp(float mA) { return Math.round(mA * 1000f); } // 设备若要 μA，改用这行

    /** 档位(1..40) + 模式 => 电流(mA) */
    private float gearToCurrentmA(int gearRaw, int mode) {
        int gear = Math.max(1, Math.min(gearRaw, 40));
        if (mode == STIM_MODE_TP) {     // 经皮：1..40 mA
            return gear;
        } else {                        // 经针：1..4 mA（线性）
            return 1f + (gear - 1) * (3f / 39f);
        }
    }

    /** 立刻推送当前模式+档位对应的电流到硬件 */
    private void pushCurrentToDevice() {
        try {
            int gear = parseInt(tvGearValue);
            float mA = gearToCurrentmA(gear, stimMode);
            int valueForDevice = mAToDeviceDeci(mA); // 若改 μA，替换成 mAToMicroAmp(mA)

            org.json.JSONObject obj = new org.json.JSONObject();
            obj.put("cmd", "set_current");
            obj.put("mode", (stimMode == STIM_MODE_TP) ? "transcutaneous" : "needle");
            obj.put("gear", gear);
            obj.put("current_mA", (double) mA);
            obj.put("current_deci_mA", valueForDevice);

            byte[] payload = obj.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8);

            if (opCode == null || opCode.isEmpty()) {
                Log.e(TAG, "opCode 为空，无法发送 set_current");
                return;
            }
            int op = Integer.decode(opCode);
            sendVendorModelMessage(op, payload, true);
            Log.d(TAG, "已发送 set_current: " + obj);
        } catch (Exception e) {
            Log.e(TAG, "pushCurrentToDevice 失败", e);
        }
    }

    private Handler handler = new Handler(Looper.getMainLooper());
    private Runnable dataCollectorRunnable;
    // —— 电流发送的防抖 ——
    private final Handler currentDebounceHandler = new Handler(Looper.getMainLooper());
    private Runnable currentDebounceRunnable;

    private boolean isCollecting = false;

    // 在 CustomControlsActivity 类中添加成员变量
    private LogRecord currentLogRecord;
    private Date sessionStartTime;
    private List<Float> voltage1History = new ArrayList<>();
    private List<Float> voltage2History = new ArrayList<>();
    private List<Float> currentHistory = new ArrayList<>();
    private List<Float> impedanceHistory = new ArrayList<>();
    // 保存从硬件接收到的最新DeviceStatus数据
    private DeviceStatus latestDeviceStatus = null;

    // 添加公共方法供HardwareCommunicationTester使用
    public DeviceStatus getLatestDeviceStatus() {
        return latestDeviceStatus;
    }

    public boolean isCollectingData() {
        return isCollecting;
    }


    // 在 CustomControlsActivity 类中添加辅助方法
    private float calculateAverage(List<Float> values) {
        if (values == null || values.isEmpty()) return 0;

        float sum = 0;
        for (float value : values) {
            sum += value;
        }
        return sum / values.size();
    }

    // 更新 startLogRecord 方法
    private void startLogRecord() {
        Log.d(TAG, "开始记录日志");
        currentLogRecord = new LogRecord();
        currentLogRecord.startTime = new Date();
        Log.d(TAG, "日志开始时间: " + currentLogRecord.startTime);

        // 保存用户设置参数
        currentLogRecord.gear = parseInt(tvGearValue);
        currentLogRecord.totalTime = parseInt(tvTimeValue); // 修复：正确获取总时间
        currentLogRecord.totalSegments = parseInt(tvTotalSegmentsValue);
        Log.d(TAG, "保存用户设置: gear=" + currentLogRecord.gear +
                ", totalTime=" + currentLogRecord.totalTime +
                ", totalSegments=" + currentLogRecord.totalSegments);

        // 保存段参数为JSON
        saveCurrentSegmentParams(); // 确保当前段参数已保存
        currentLogRecord.segmentParams = new Gson().toJson(segmentParams);
        Log.d(TAG, "段参数已保存: " + currentLogRecord.segmentParams);

        // 初始化历史数据列表
        voltage1History.clear();
        voltage2History.clear();
        currentHistory.clear();
        impedanceHistory.clear();
        Log.d(TAG, "历史数据列表已初始化");
    }

    // 更新 startCountdown 方法确保调用 startLogRecord
    private void startCountdown() {
        Log.d(TAG, "开始倒计时");
        if (remainingMs <= 0) {
            remainingMs = parseInt(tvTimeValue) * 60_000L;
            if (remainingMs <= 0) {
                Toast.makeText(this, "请先设置时间", Toast.LENGTH_SHORT).show();
                return;
            }
        }
        isCounting = true;
        btnPlay.setText("⏸");

        // 👉 开始记录日志（确保这行存在）
        startLogRecord(); // 确保在开始倒计时前调用
        Log.d(TAG, "日志记录已启动");

        // 👉 开始采集硬件数据
        startCollectingReceivedData();

        // 运行数据转换测试
        DataConversionTester.testDataConversion();
        DataConversionTester.testNullHandling();

        // 开始硬件通信监控
        HardwareCommunicationTester.startCommunicationMonitoring(this);

        countDownTimer = new CountDownTimer(remainingMs, 1000) {
            @Override
            public void onTick(long m) {
                remainingMs = m;
                updateCountdownLabel();
            }

            @Override
            public void onFinish() {
                isCounting = false;
                remainingMs = 0;
                updateCountdownLabel();
                btnPlay.setText("▶");
                Toast.makeText(CustomControlsActivity.this, "倒计时结束", Toast.LENGTH_SHORT).show();

                // 👉 结束采集
                stopCollectingReceivedData();
                // 👉 保存日志
                stopCountdown();
            }
        }.start();
        Log.d(TAG, "倒计时开始: " + remainingMs + "ms");
    }

    private void stopCollectingReceivedData() {
        isCollecting = false;
        handler.removeCallbacks(dataCollectorRunnable);

        // 停止硬件通信监控
        HardwareCommunicationTester.stopCommunicationMonitoring();
    }

    // 更新 startCollectingReceivedData 方法
    private void startCollectingReceivedData() {
        Log.d(TAG, "开始收集数据");
        if (isCollecting) {
            Log.d(TAG, "数据收集已在进行中");
            return;
        }
        isCollecting = true;

        dataCollectorRunnable = new Runnable() {
            public float getImpedanceFromHardware() {
                // 使用从硬件接收到的真实阻抗数据
                if (latestDeviceStatus != null) {
                    float value = ImpedanceConverter.convertToOhms(latestDeviceStatus.getResistance()); // 转换为Ω
                    Log.d(TAG, "获取真实阻抗数据: " + ImpedanceConverter.formatImpedance(value));
                    return value;
                }
                // 如果没有硬件数据，使用默认值
                return 0;
            }

            public float getCurrentFromHardware() {
                // 使用从硬件接收到的真实电流数据
                if (latestDeviceStatus != null) {
                    float value = (float) latestDeviceStatus.getCurrent() / 1000; // 转换为A
                    Log.d(TAG, "获取真实电流数据: " + value + " A");
                    return value;
                }
                // 如果没有硬件数据，使用默认值
                return 0;
            }

            public float getVoltage2FromHardware() {
                // 使用从硬件接收到的真实电压2数据
                if (latestDeviceStatus != null) {
                    float value = (float) latestDeviceStatus.getVoltage1() / 1000; // 转换为V
                    Log.d(TAG, "获取真实电压2数据: " + value + " V");
                    return value;
                }
                // 如果没有硬件数据，使用默认值
                return 0;
            }

            public float getVoltage1FromHardware() {
                // 使用从硬件接收到的真实电压1数据
                if (latestDeviceStatus != null) {
                    float value = (float) latestDeviceStatus.getVoltage0() / 1000; // 转换为V
                    Log.d(TAG, "获取真实电压1数据: " + value + " V");
                    return value;
                }
                // 如果没有硬件数据，使用默认值
                return 0;
            }

            @Override
            public void run() {
                if (!isCollecting) {
                    Log.d(TAG, "数据收集已停止");
                    return;
                }

                // 获取硬件数据
                float voltage1 = getVoltage1FromHardware();
                float voltage2 = getVoltage2FromHardware();
                float current = getCurrentFromHardware();
                float impedance = getImpedanceFromHardware();

                // 添加到历史记录
                voltage1History.add(voltage1);
                voltage2History.add(voltage2);
                currentHistory.add(current);
                impedanceHistory.add(impedance);

                Log.d(TAG, "数据已添加到历史记录: V1=" + voltage1 + ", V2=" + voltage2 +
                        ", Current=" + current + ", Impedance=" + impedance);

                // 封装成实体
                ReceivedProfile profile = new ReceivedProfile();
                profile.voltage1 = voltage1;
                profile.voltage2 = voltage2;
                profile.current = current;
                profile.impedance = impedance;

                // 写入数据库（在后台线程）
                Executors.newSingleThreadExecutor().execute(() -> {
                    try {
                        AppDatabase.getInstance(CustomControlsActivity.this)
                                .receivedProfileDao()
                                .insertReceivedProfile(profile);
                        Log.d(TAG, "数据保存到数据库成功");
                    } catch (Exception e) {
                        Log.e(TAG, "保存数据到数据库失败", e);
                    }
                });

                // 每 1 秒重复一次
                handler.postDelayed(this, 1000);
            }
        };

        handler.post(dataCollectorRunnable);
    }



    private void stopCountdown() {
        if (countDownTimer != null) {
            countDownTimer.cancel();
            countDownTimer = null;
        }
        isCounting = false;
        btnPlay.setText("▶");
        updateCountdownLabel();
        stopCollectingReceivedData();

        // 保存日志记录 - 修复逻辑
        if (currentLogRecord != null) {
            // 确保所有必需字段都有值
            if (currentLogRecord.startTime == null) {
                currentLogRecord.startTime = new Date(System.currentTimeMillis() - (parseInt(tvTimeValue) * 60_000L - remainingMs));
            }
            currentLogRecord.endTime = new Date();
            currentLogRecord.duration = currentLogRecord.endTime.getTime() - currentLogRecord.startTime.getTime();

            // 修复：确保 totalTime 字段正确设置
            currentLogRecord.totalTime = parseInt(tvTimeValue);
            Log.d(TAG, "stopCountdown 中设置的 totalTime: " + currentLogRecord.totalTime);
            // 计算实时数据的各种统计值
            if (voltage1History != null && !voltage1History.isEmpty()) {
                currentLogRecord.avgVoltage1 = calculateAverage(voltage1History);
                currentLogRecord.maxVoltage1 = Collections.max(voltage1History);
                currentLogRecord.minVoltage1 = Collections.min(voltage1History);
            }

            if (voltage2History != null && !voltage2History.isEmpty()) {
                currentLogRecord.avgVoltage2 = calculateAverage(voltage2History);
                currentLogRecord.maxVoltage2 = Collections.max(voltage2History);
                currentLogRecord.minVoltage2 = Collections.min(voltage2History);
            }

            if (currentHistory != null && !currentHistory.isEmpty()) {
                currentLogRecord.avgCurrent = calculateAverage(currentHistory);
                currentLogRecord.maxCurrent = Collections.max(currentHistory);
                currentLogRecord.minCurrent = Collections.min(currentHistory);
            }

            if (impedanceHistory != null && !impedanceHistory.isEmpty()) {
                currentLogRecord.avgImpedance = calculateAverage(impedanceHistory);
                currentLogRecord.maxImpedance = Collections.max(impedanceHistory);
                currentLogRecord.minImpedance = Collections.min(impedanceHistory);
            }

            // 保存历史记录为JSON
            currentLogRecord.voltage1History = new Gson().toJson(voltage1History);
            currentLogRecord.voltage2History = new Gson().toJson(voltage2History);
            currentLogRecord.currentHistory = new Gson().toJson(currentHistory);
            currentLogRecord.impedanceHistory = new Gson().toJson(impedanceHistory);

            // 保存到数据库 - 添加更多调试信息
            Executors.newSingleThreadExecutor().execute(() -> {
                try {
                    long id = AppDatabase.getInstance(CustomControlsActivity.this)
                            .logRecordDao()
                            .insertLogRecord(currentLogRecord);
                    Log.d(TAG, "日志记录保存成功，ID: " + id + ", totalTime: " + currentLogRecord.totalTime);

                    // 添加验证代码
                    if (id == -1) {
                        Log.e(TAG, "日志记录插入失败，返回ID为-1");
                        // 尝试打印日志记录内容以调试
                        Log.e(TAG, "尝试插入的日志记录: " + new Gson().toJson(currentLogRecord));
                    } else {
                        Log.d(TAG, "日志记录插入成功");
                    }

                    // 验证数据是否真的保存成功
                    List<LogRecord> records = AppDatabase.getInstance(CustomControlsActivity.this)
                            .logRecordDao()
                            .getAllLogRecordsSync();
                    Log.d(TAG, "当前数据库中的记录数: " + records.size());
                } catch (Exception e) {
                    Log.e(TAG, "保存日志记录失败", e);
                }
            });

            currentLogRecord = null;
        } else {
            Log.w(TAG, "currentLogRecord 为 null，无法保存日志");
        }

        Log.d(TAG, "倒计时停止");
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == 100 && resultCode == RESULT_OK && data != null) {
            SentProfile selectedProfile = (SentProfile) data.getSerializableExtra("selected_profile");
            if (selectedProfile != null) {
                ((TextView) findViewById(R.id.tvTotalSegmentsValue)).setText(String.valueOf(selectedProfile.totalSegment));
                // 加上单位
                ((TextView) findViewById(R.id.tvDurationValue)).setText(selectedProfile.segmentDuration + " 分钟");
                ((TextView) findViewById(R.id.tvGearValue)).setText(String.valueOf(selectedProfile.currentBlock));

                // 设置波形类型 Spinner 选中项
                ((Spinner) findViewById(R.id.spinnerWaveformType)).setSelection(getWaveformTypeIndex(selectedProfile.waveformType));

                ((TextView) findViewById(R.id.tvPulseWidthValue)).setText(selectedProfile.pulseWidth + " ms");
                ((TextView) findViewById(R.id.tvFrequencyValue)).setText(selectedProfile.frequency + " Hz");
            }
        }
    }

    // 获取波形类型的索引
    private int getWaveformTypeIndex(String waveformType) {
        String[] waveformTypes = {"正向正弦波", "负向正弦波", "双向正弦波",
                "正向三角波", "负向三角波", "双向三角波",
                "正向脉冲波", "负向脉冲波", "双向脉冲波"};
        for (int i = 0; i < waveformTypes.length; i++) {
            if (waveformTypes[i].equals(waveformType)) {
                return i;
            }
        }
        return 0;  // 默认选择第一个

    }

    @Override
    protected void onPause() {
        super.onPause();
        persistState();

        // —— 停止输出波形动画 ——
        waveAnimRunning = false;
        if (waveAnimRunnable != null) {
            waveAnimHandler.removeCallbacks(waveAnimRunnable);
        }
    }

//    private void onBluetoothMeshPressed() {
//        // 跳转前保存状态，防止Activity销毁后丢失
//        persistState();
//        startActivity(new Intent(this, MainActivity.class));
//        finish();
//    }

    private void onSaveDataPressed() {
        // 先保存当前段的参数
        saveCurrentSegmentParams();

        // 弹出配置名称输入对话框
        showConfigNameInputDialog();
    }

    private void showConfigNameInputDialog() {
        // 创建输入框
        final EditText input = new EditText(this);
        input.setHint("请输入配置名称");
        input.setSingleLine(true);

        new AlertDialog.Builder(this)
                .setTitle("保存配置")
                .setMessage("请输入配置名称：")
                .setView(input)
                .setPositiveButton("保存", (dialog, which) -> {
                    String configName = input.getText().toString().trim();
                    if (configName.isEmpty()) {
                        Toast.makeText(this, "配置名称不能为空", Toast.LENGTH_SHORT).show();
                        return;
                    }

                    // 检查配置名称是否重复
                    Executors.newSingleThreadExecutor().execute(() -> {
                        boolean nameExists = viewModel.isConfigNameExists(configName);
                        runOnUiThread(() -> {
                            if (nameExists) {
                                Toast.makeText(this, "配置名称已存在，请使用其他名称", Toast.LENGTH_SHORT).show();
                            } else {
                                // 名称不重复，开始保存
                                saveGroupConfig(configName);
                            }
                        });
                    });
                })
                .setNegativeButton("取消", null)
                .show();
    }

    private void saveGroupConfig(String configName) {
        try {
            int totalSegments = parseInt(tvTotalSegmentsValue);
            int currentGear = Integer.parseInt(tvGear.getText().toString());

            // 获取波形类型字符串数组
            String[] waveformTypes = getResources().getStringArray(R.array.waveform_types);

            // 使用后台线程处理所有保存操作
            Executors.newSingleThreadExecutor().execute(() -> {
                // 1. 先构建当前要保存的组配置
                List<SentProfile> currentGroup = new ArrayList<>();
                for (int segmentIndex = 1; segmentIndex <= totalSegments; segmentIndex++) {
                    // 获取该段的参数
                    SegmentParams segmentParams = CustomControlsActivity.this.segmentParams.get(segmentIndex);
                    if (segmentParams == null) {
                        // 如果该段参数不存在，创建默认参数
                        segmentParams = new SegmentParams();
                        segmentParams.segmentRepeat = DEFAULT_SEGMENT_REPEAT;
                        segmentParams.durationMin = DEFAULT_DURATION_MS;
                        segmentParams.waveformType = DEFAULT_WAVEFORM_TYPE;
                        segmentParams.pulseWidthMs = DEFAULT_PULSE_WIDTH_US;
                        segmentParams.frequencyHz = DEFAULT_FREQUENCY_HZ;
                        segmentParams.waveformRepeat = DEFAULT_WAVEFORM_REPEAT;
                        CustomControlsActivity.this.segmentParams.put(segmentIndex, segmentParams);
                    }

                    // 创建配置对象
                    SentProfile sentProfile = new SentProfile();
                    sentProfile.segmentDuration = segmentParams.durationMin;
                    sentProfile.waveformType = waveformTypes[segmentParams.waveformType];
                    sentProfile.pulseWidth = segmentParams.pulseWidthMs;
                    sentProfile.frequency = segmentParams.frequencyHz;
                    sentProfile.segmentIndex = segmentIndex;
                    sentProfile.configName = configName; // 设置配置名称
                    currentGroup.add(sentProfile);
                }

                // 2. 保存整组配置
                String groupId = "group_" + System.currentTimeMillis() + "_" + totalSegments;
                int savedCount = 0;

                for (SentProfile sentProfile : currentGroup) {
                    sentProfile.groupId = groupId; // 设置组ID
                    viewModel.insertSentProfileSync(sentProfile);
                    savedCount++;
                    Log.d("DatabaseSave", "保存段 " + sentProfile.segmentIndex + " 数据: " + sentProfile.toString());
                }

                // 显示保存结果
                int finalSavedCount = savedCount;
                runOnUiThread(() -> {
                    Toast.makeText(CustomControlsActivity.this,
                            "配置 '" + configName + "' 保存成功（" + finalSavedCount + "段）", Toast.LENGTH_SHORT).show();
                    // 更新当前配置名称，确保后续刺激时能正确记录
                    currentConfigName = configName;
                });
            });

        } catch (Exception e) {
            Toast.makeText(this, "保存数据失败: " + e.getMessage(), Toast.LENGTH_LONG).show();
            Log.e(TAG, "保存异常", e);
        }
    }

    public void sendVendorModelMessage(final int opcode, final byte[] parameters, final boolean acknowledged) {
        Log.d(TAG, "sendVendorModelMessage");
        final Element element = mViewModel.getSelectedElement().getValue();
        Log.d(TAG, "element:" + element);
        if (element == null) {
            Log.w(TAG, "Selected element is null, abort send.");
            return;
        }
        final VendorModel model = (VendorModel) mViewModel.getSelectedModel().getValue();
        Log.d(TAG, "model:" + model);
        if (model == null) {
            Log.w(TAG, "Selected VendorModel is null, abort send.");
            return;
        }
        final java.util.List<Integer> boundIndexes = model.getBoundAppKeyIndexes();
        if (boundIndexes == null || boundIndexes.isEmpty()) {
            Log.w(TAG, "VendorModel has no bound AppKey, cancel sending.");
            return;
        }
        final int appKeyIndex = boundIndexes.get(0);
        Log.d(TAG, "appKeyIndex:" + appKeyIndex);
        final ApplicationKey appKey = mViewModel.getNetworkLiveData().getMeshNetwork().getAppKey(appKeyIndex);
        Log.d(TAG, "appKey:" + appKey);
        if (appKey == null) {
            Log.e(TAG, "AppKey resolved as null, abort send.");
            return;
        }

        final ProvisionedMeshNode selectedNode = mViewModel.getSelectedMeshNode().getValue();
        final String selectedNodeDesc = selectedNode != null ? selectedNode.getNodeName() + "(" + selectedNode.getUnicastAddress() + ")" : "null";
        Log.d(TAG, "Prepared send → dstElementAddr=" + element.getElementAddress()
                + ", node=" + selectedNodeDesc
                + ", companyId=" + model.getCompanyIdentifier()
                + ", modelId=" + model.getModelId()
                + ", opcode=" + opcode
                + ", payloadLen=" + (parameters != null ? parameters.length : 0));

        if (acknowledged) {
            sendAcknowledgedMessage(element.getElementAddress(),
                    new VendorModelMessageAcked(appKey, model.getModelId(), model.getCompanyIdentifier(), opcode, parameters));
        } else {
            // Use Unacked for unacknowledged messages
            sendUnacknowledgedMessage(element.getElementAddress(),
                    new VendorModelMessageUnacked(appKey, model.getModelId(), model.getCompanyIdentifier(), opcode, parameters));
        }
    }

    private void drawRectPulse(Path path, float startTimeMs, float pulseMs, float centerY, float amp, float timeToPixel) {
        float startX = startTimeMs * timeToPixel;
        float pulseW = pulseMs * timeToPixel;

        path.moveTo(startX, centerY);
        path.lineTo(startX, centerY - amp);
        path.lineTo(startX + pulseW, centerY - amp);
        path.lineTo(startX + pulseW, centerY);
    }

    private void drawTrianglePulse(Path path, float startMs, float pulseMs, float centerY, float amp, float timeToPixel) {
        float startX = startMs * timeToPixel;
        float halfW = (pulseMs * timeToPixel) / 2f;

        path.moveTo(startX, centerY);
        path.lineTo(startX + halfW, centerY - amp);
        path.lineTo(startX + 2 * halfW, centerY);
    }


    private void drawSinePulse(Path path, float startMs, float pulseMs, float centerY, float amp, float timeToPixel) {
        float startX = startMs * timeToPixel;
        float endX = (startMs + pulseMs) * timeToPixel;

        int steps = 50;
        for (int i = 0; i <= steps; i++) {
            float t = i / (float) steps;
            float x = startX + t * (endX - startX);
            float y = centerY - (float)(amp * Math.sin(Math.PI * t));  // 上扬正弦
            if (i == 0) path.moveTo(x, y);
            else path.lineTo(x, y);
        }
    }


    private static class SegmentParams {
        int segmentRepeat, durationMin, waveformType;
        int pulseWidthMs, frequencyHz, waveformRepeat;
    }

    private static class ParameterState {
        int totalSegments;
        int gear;
        int timeMin;
        long remainingMs;
        Map<Integer, SegmentParams> segmentParams;
        int selectedSegment;
        List<Integer> bandOrder;
        int stimMode;  // 经皮/经针：0=经皮，1=经针
    }

    @Override
    protected void updateMeshMessage(final MeshMessage meshMessage) {
        super.updateMeshMessage(meshMessage);

        if (meshMessage instanceof VendorModelMessageStatus) {
            final VendorModelMessageStatus status = (VendorModelMessageStatus) meshMessage;
            final int opcode = status.getOpCode();
            final byte[] params = status.getParameters();

            if (opcode == 0x1) {
                // 1. 解析 JSON 数据
                final String json = new String(params, StandardCharsets.UTF_8);
                Log.d("MeshMessage", "收到 C1 指令 JSON: " + json);

                try {
                    DeviceStatus deviceStatus = new Gson().fromJson(json, DeviceStatus.class);
                    if (deviceStatus != null) {
                        // 保存最新的DeviceStatus数据
                        latestDeviceStatus = deviceStatus;
                        Log.d("MeshMessage", "硬件数据已更新: " + deviceStatus.toString());
                    } else {
                        Log.w("MeshMessage", "⚠️ JSON解析后DeviceStatus为null");
                    }
                } catch (Exception e) {
                    Log.e("MeshMessage", "JSON 解析失败: " + e.getMessage());
                }

            } else if (opcode == 0xC202E5) {
                // 2. 处理其他指令
                Log.d("MeshMessage", "收到 C2 响应");
            }

        } else if (meshMessage instanceof ConfigVendorModelAppList) {
            final ConfigVendorModelAppList status = (ConfigVendorModelAppList) meshMessage;
            mViewModel.removeMessage();
            if (status.isSuccessful()) {
                if (handleStatuses()) return;
            } else {
                displayStatusDialogFragment(getString(R.string.title_vendor_model_app_list), status.getStatusCodeName());
            }

        } else if (meshMessage instanceof ConfigVendorModelSubscriptionList) {
            Log.d(TAG, "收到ConfigVendorModelSubscriptionList消息");
            final ConfigVendorModelSubscriptionList status = (ConfigVendorModelSubscriptionList) meshMessage;
            mViewModel.removeMessage();
            if (status.isSuccessful()) {
                if (handleStatuses()) return;
            } else {
                displayStatusDialogFragment(getString(R.string.title_vendor_model_subscription_list), status.getStatusCodeName());
            }
        } else {
            Log.d(TAG, "其他类型消息: " + meshMessage.getClass().getSimpleName());
        }

        Log.d(TAG, "=== Mesh消息处理完成 ===");
        hideProgressBar();
    }

    /**
     * 处理节点点击事件，与NetworkFragment行为保持一致
     */
    private void handleNodeClick(ProvisionedMeshNode node) {
        // 使用统一的节点选中状态处理方法（保留原有选择/取消选择逻辑）
        handleNodeSelection(node);

        // 每次点击都下发颜色；若无颜色则先分配再下发
        if (node != null) {
            String nodeKey = String.valueOf(node.getUnicastAddress());
            Integer assigned = firstClickAssignedColor.get(nodeKey);
            int color;
            if (assigned == null) {
                color = nextColorToAssign;
                firstClickAssignedColor.put(nodeKey, color);
                int next = (nextColorToAssign % 6) + 1;
                nextColorToAssign = next;
                if (mNodeAdapter != null) mNodeAdapter.setAssignedColors(firstClickAssignedColor);
                if (deviceSpinnerAdapter != null) deviceSpinnerAdapter.notifyDataSetChanged();
            } else {
                color = assigned;
            }
            sendColorJsonToNode(node, color);
        }
    }

    /**
     * 统一处理节点选中状态及UI变化的方法
     * 支持多选功能：允许同时选中多个节点
     */
    private void handleNodeSelection(ProvisionedMeshNode node) {
        // 检查节点是否已选中（按地址判断）
        boolean isSelected = false;
        for (ProvisionedMeshNode sn : selectedNodes) {
            if (sn != null && sn.getUnicastAddress() == node.getUnicastAddress()) {
                isSelected = true;
                break;
            }
        }

        if (isSelected) {
            // 取消选择：从选中列表移除节点
            deselectNode(node);
        } else {
            // 选中节点：添加到选中列表
            selectNode(node);
        }

        // 更新NodeAdapter的UI状态
        updateNodeAdapterSelection();

        // 检查是否需要建立mesh组网
        checkAndCreateMeshNetwork();
    }

    /**
     * 选中节点的统一处理方法
     */
    private void selectNode(ProvisionedMeshNode node) {
        // 1. 添加到选中节点列表
        addNodeToSelection(node);

        // 2. 更新ViewModel中的选中状态（保持与最后选中的节点一致）
        mViewModel.setSelectedMeshNode(node);

        // 3. 自动配置AppKey和VendorModel
        autoConfigureNode(node);

        // 4. 如果节点之前被阻塞，按地址匹配解除阻塞状态
        ProvisionedMeshNode toRemove = null;
        for (ProvisionedMeshNode bn : blockedNodes) {
            if (bn != null && bn.getUnicastAddress() == node.getUnicastAddress()) {
                toRemove = bn;
                break;
            }
        }
        if (toRemove != null) {
            blockedNodes.remove(toRemove);
            Log.d(TAG, "节点 " + node.getNodeName() + " 已解除阻塞状态（按地址匹配）");
        }

        // 维护阻塞状态：未选中即阻塞，选中即解除阻塞
        handleDataBlocking();
    }

    /**
     * 取消选中节点的统一处理方法
     */
    private void deselectNode(ProvisionedMeshNode node) {
        // 1. 从选中节点列表中移除
        removeNodeFromSelection(node);

        // 2. 如果这是ViewModel中的当前选中节点，清除选中状态
        if (mViewModel.getSelectedMeshNode().getValue() != null &&
                mViewModel.getSelectedMeshNode().getValue().getUnicastAddress() == node.getUnicastAddress()) {
            mViewModel.clearSelectedMeshNode();
        }

        // 维护阻塞状态：未选中即阻塞，选中即解除阻塞
        handleDataBlocking();
    }

    /**
     * 更新NodeAdapter的选中状态显示
     */
    private void updateNodeAdapterSelection() {
        if (mNodeAdapter != null) {
            // 更新多选节点列表
            mNodeAdapter.setSelectedNodes(selectedNodes);
            // 更新阻塞节点集合
            mNodeAdapter.setBlockedNodes(blockedNodes);

            // 强制刷新RecyclerView布局，确保高度正确计算
            mNodesRecyclerView.post(() -> {
                mNodesRecyclerView.requestLayout();
                mNodesRecyclerView.invalidate();
            });

            Log.d(TAG, "已更新UI状态 - 选中节点: " + selectedNodes.size() + ", 阻塞节点: " + blockedNodes.size());
        }
    }

    /**
     * 自动配置节点的AppKey和VendorModel
     */
    private void autoConfigureNode(ProvisionedMeshNode node) {
        try {
            // 1. 初始化默认AppKey
            mMeshAutoConfigurator.initDefaultAppKey(mViewModel.getMeshManagerApi());

            // 2. 自动绑定所有VendorModel
            mMeshAutoConfigurator.autoBindAllVendorModels(node, mViewModel.getMeshManagerApi());

            // 3. 设置opcode为0xC0
            setOpCodeForVendorModels("0xC0");

            // 4. 确保节点被标记为已配置
            if (!node.isConfigured()) {
                node.setConfigured(true);
            }
        } catch (Exception e) {
            Toast.makeText(this, "AppKey自动配置失败: " + e.getMessage(), Toast.LENGTH_LONG).show();
        }
    }

    /**
     * 设置VendorModel的opcode
     */
    private void setOpCodeForVendorModels(String opCode) {
        SharedPreferences prefs = getSharedPreferences("AppPrefs", MODE_PRIVATE);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putString("opCode", opCode);
        editor.apply();
    }

    //蓝牙权限
    private void checkAndRequestBluetoothPermissions() {
        List<String> permissions = new ArrayList<>();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            // Android 12+
            if (checkSelfPermission(Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
                permissions.add(Manifest.permission.BLUETOOTH_SCAN);
            }
            if (checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
                permissions.add(Manifest.permission.BLUETOOTH_CONNECT);
            }
        } else {
            // Android 11 及以下
            if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                permissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
            }
        }

        if (!permissions.isEmpty()) {
            // 在请求权限前添加标记，防止首次启动时的同意蓝牙权限后的闪退问题
            Utils.markBluetoothPermissionsRequested(this);
            requestPermissions(permissions.toArray(new String[0]), REQUEST_BLUETOOTH_PERMISSIONS);
        }
    }

    //蓝牙权限，用户选择处理
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == REQUEST_BLUETOOTH_PERMISSIONS) {
            boolean granted = true;
            for (int result : grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
                    granted = false;
                    break;
                }
            }
            if (granted) {
                Toast.makeText(this, "蓝牙权限已开启", Toast.LENGTH_SHORT).show();
                maybeStartScan(); // 授权后立刻尝试扫描
            } else {
                // 用户拒绝 → 提示去设置
                new AlertDialog.Builder(this)
                        .setTitle("需要蓝牙权限")
                        .setMessage("应用需要蓝牙权限才能扫描和连接设备，请在设置中开启。")
                        .setPositiveButton("去设置", (d, w) -> {
                            Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                            intent.setData(Uri.fromParts("package", getPackageName(), null));
                            startActivity(intent);
                        })
                        .setNegativeButton("取消", null)
                        .show();
            }
        }
    }

    private void maybeStartScan() {
        // 配网期间不启动扫描
        if (mProvisioningActive.get()) return;

        final ScannerStateLiveData s = mScannerVM.getScannerRepository().getScannerState().getValue();
        if (s == null) return;

        final boolean permOk = Utils.isBluetoothScanAndConnectPermissionsGranted(this);
        final boolean locOk = !Utils.isLocationRequired(this) || Utils.isLocationPermissionsGranted(this);

        if (permOk && locOk && s.isBluetoothEnabled() && !s.isScanning()) {
            // 若你只想显示“未配网设备”，用配网 UUID：
            mScannerVM.getScannerRepository().startScan(BleMeshManager.MESH_PROVISIONING_UUID);
        }
    }

    private String makeDefaultNodeName(final ExtendedBluetoothDevice d) {
        final String name = d.getName() != null ? d.getName() : "Node";
        final String addrTail = d.getAddress() != null
                ? d.getAddress().replace(":", "")
                : String.valueOf(System.currentTimeMillis() % 10000);
        return name + "-" + addrTail.substring(Math.max(0, addrTail.length() - 4));
    }

    // 配网期间的忙碌提示，这里简单用副标题与禁用点击
    private void setBusyUi(boolean busy, @Nullable String subtitle) {
        // 不重置点击监听器，仅进行提示；真正的拦截逻辑在 onCreate 中的监听器里通过 headless/mCurrentProvisioningDevice 判定
        if (busy) {
            if (!TextUtils.isEmpty(subtitle)) {
                Log.d(TAG, subtitle);
            }
        }
    }

    // OOB 输入弹窗（静态/Input/Output 共用）
    private void showOobDialog(MeshHeadlessProvisioner.OobRequest req) {
        final EditText et = new EditText(this);
        et.setHint("输入 OOB/PIN");

        String msg;
        if (req.isStatic) msg = "请输入静态 OOB";
        else if (req.isInput) msg = "请输入设备要求的 PIN";
        else if (req.isOutput) msg = "请输入设备显示/反馈的值";
        else msg = "请输入 OOB";

        new AlertDialog.Builder(this)
                .setTitle("需要 OOB 验证")
                .setMessage(msg)
                .setView(et)
                .setNegativeButton(android.R.string.cancel, (d, w) -> {
                    if (headless != null) headless.cancel();
                })
                .setPositiveButton(android.R.string.ok, (d, w) -> {
                    if (headless != null) headless.submitPin(et.getText().toString().trim());
                })
                .show();
    }

    private String safeName(ExtendedBluetoothDevice d) {
        return (d.getName() != null ? d.getName() : "Node") + "/" + (d.getAddress() != null ? d.getAddress() : "--");
    }

    // 设置过滤后的节点列表，并对比上次显示项，发现“消失的节点”后弹出离线提示
    private void setFilteredNodesWithOfflineToast(List<ProvisionedMeshNode> newList) {
        // 统一交给 NodeOnlineHelper 处理离线 Toast 与设置列表
        if (onlineHelper != null) {
            onlineHelper.setFilteredNodesWithOfflineToast(mFilteredNodes, newList);
        } else {
            mFilteredNodes.setValue(newList);
        }
    }

    private void refreshFilteredNodes() {
        List<ProvisionedMeshNode> nodes = mViewModel.getNodes().getValue();
        if (nodes != null) {
            final List<ProvisionedMeshNode> filtered = onlineHelper != null
                    ? onlineHelper.filterOnlineWithDelay(nodes, mPendingVisibility, DelayTime_ms)
                    : nodes;
            setFilteredNodesWithOfflineToast(filtered);
        }
    }

    // —— 新增：节点选择管理方法 ——
    private void addNodeToSelection(ProvisionedMeshNode node) {
        // 基于地址判断是否已选中，避免对象引用误判
        boolean alreadySelected = false;
        for (ProvisionedMeshNode sn : selectedNodes) {
            if (sn != null && sn.getUnicastAddress() == node.getUnicastAddress()) {
                alreadySelected = true;
                break;
            }
        }
        if (!alreadySelected) {
            boolean isFirstNode = selectedNodes.isEmpty();
            selectedNodes.add(node);
            updateDeviceSpinner();

            // 如果这是第一个添加的节点，自动选中它（优先使用已恢复的索引）
            if (isFirstNode) {
                int maxIdx = 0;
                if (deviceSpinner.getAdapter() != null) {
                    maxIdx = Math.max(0, deviceSpinner.getAdapter().getCount() - 1);
                }
                int targetIndex;
                if (currentDeviceIndex > 0) {
                    targetIndex = Math.min(currentDeviceIndex, maxIdx);
                    // 确保至少选中第一个实际设备（索引1），避免落在占位项0
                    if (targetIndex < 1 && maxIdx >= 1) {
                        targetIndex = 1;
                    }
                } else {
                    // 没有已恢复的索引时，默认选中第一个实际设备（索引1）
                    targetIndex = Math.min(1, maxIdx);
                }
                deviceSpinner.setSelection(targetIndex);
            }

            checkAndCreateMeshNetwork();
        }
    }

    private void removeNodeFromSelection(ProvisionedMeshNode node) {
        // 基于地址移除，避免对象引用误判导致移除失败
        ProvisionedMeshNode toRemove = null;
        for (ProvisionedMeshNode sn : selectedNodes) {
            if (sn != null && sn.getUnicastAddress() == node.getUnicastAddress()) {
                toRemove = sn;
                break;
            }
        }
        if (toRemove != null) {
            selectedNodes.remove(toRemove);
            updateDeviceSpinner();
        }
    }

    // 将所有离线节点从选中集合中移除，并清理 ViewModel 的选中状态
    private void pruneSelectionForOfflineNodes() {
        if (selectedNodes.isEmpty()) return;
        List<ProvisionedMeshNode> toRemove = new ArrayList<>();
        for (ProvisionedMeshNode sn : selectedNodes) {
            if (sn != null && (onlineHelper == null || !onlineHelper.isOnline(sn.getUnicastAddress()))) {
                toRemove.add(sn);
            }
        }
        for (ProvisionedMeshNode rm : toRemove) {
            removeNodeFromSelection(rm);
        }
        // 如 ViewModel 当前选中节点不在线，清除选中状态
        final ProvisionedMeshNode sel = mViewModel.getSelectedMeshNode().getValue();
        if (sel != null && (onlineHelper == null || !onlineHelper.isOnline(sel.getUnicastAddress()))) {
            mViewModel.clearSelectedMeshNode();
        }
        updateNodeAdapterSelection();
    }

    private void updateDeviceSpinner() {
        deviceSpinnerItems.clear();
        deviceSpinnerItems.add("选择设备");
        spinnerIndexToNodeMap.clear();

        // 添加选中的节点到spinner，建立一对一映射关系
        for (int i = 0; i < selectedNodes.size(); i++) {
            ProvisionedMeshNode node = selectedNodes.get(i);
            String nodeName = node.getNodeName() != null ? node.getNodeName() : "Node " + node.getUnicastAddress();
            String nodeKey = String.valueOf(node.getUnicastAddress());
            // 始终使用原始设备名称进行显示
            deviceSpinnerItems.add(nodeName);

            // 建立设备索引与节点的一对一映射关系
            int deviceIndex = i + 1; // +1 因为第一项是"选择设备"
            spinnerIndexToNodeMap.put(deviceIndex, node);

            Log.d(TAG, "设备映射: 索引 " + deviceIndex + " -> 节点 " + nodeName + " (地址: " + node.getUnicastAddress() + ")");
        }

        // 迁移旧的索引键配置到字符串节点键
        if (!legacyIndexSettingsPending.isEmpty()) {
            for (Map.Entry<Integer, ParameterState> e : legacyIndexSettingsPending.entrySet()) {
                ProvisionedMeshNode mappedNode = spinnerIndexToNodeMap.get(e.getKey());
                if (mappedNode != null) {
                    String nodeKey = String.valueOf(mappedNode.getUnicastAddress());
                    if (!deviceSettings.containsKey(nodeKey)) {
                        deviceSettings.put(nodeKey, e.getValue());
                    }
                }
            }
            legacyIndexSettingsPending.clear();
        }

        deviceSpinnerAdapter.notifyDataSetChanged();
        Log.d(TAG, "设备Spinner已更新，共 " + selectedNodes.size() + " 个设备");
    }

    /**
     * 将颜色编号映射为实际文本颜色（Spinner项显示用）
     */
    private int colorIntFor(int color) {
        switch (color) {
            case 1: return Color.parseColor("#FFC0CB"); // 粉色
            case 2: return Color.GREEN;   //绿色
            case 3: return Color.YELLOW;  //黄色
            case 4: return Color.MAGENTA; //紫色
            case 5: return Color.CYAN;    //青色
            case 6: return Color.rgb(255, 165, 0); //橙色
            default: return Color.BLACK;
        }
    }

    /**
     * 设备下拉列表彩色文本适配器：不改名，仅根据已分配颜色渲染文本颜色
     */
    private class ColoredDeviceAdapter extends ArrayAdapter<String> {
        ColoredDeviceAdapter(int resource, List<String> items) {
            super(CustomControlsActivity.this, resource, items);
            setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        }

        @Override
        public View getView(int position, View convertView, android.view.ViewGroup parent) {
            View v = super.getView(position, convertView, parent);
            applyColorToView(v, position);
            return v;
        }

        @Override
        public View getDropDownView(int position, View convertView, android.view.ViewGroup parent) {
            View v = super.getDropDownView(position, convertView, parent);
            applyColorToView(v, position);
            return v;
        }

        private void applyColorToView(View v, int position) {
            TextView tv = v instanceof TextView ? (TextView) v : v.findViewById(android.R.id.text1);
            if (tv == null) return;

            if (position == 0) {
                tv.setTextColor(Color.GRAY);
                return;
            }

            ProvisionedMeshNode node = spinnerIndexToNodeMap.get(position);
            if (node != null) {
                String nodeKey = String.valueOf(node.getUnicastAddress());
                Integer assigned = firstClickAssignedColor.get(nodeKey);
                if (assigned != null) {
                    tv.setTextColor(colorIntFor(assigned));
                } else {
                    tv.setTextColor(Color.BLACK);
                }
            } else {
                tv.setTextColor(Color.BLACK);
            }
        }
    }

    /**
     * 发送 {"color":n} JSON 到指定节点（Vendor 模型）
     */
    private void sendColorJsonToNode(ProvisionedMeshNode node, int color) {
        if (node == null) return;
        try {
            // 选择该节点及其 VendorModel
            mViewModel.setSelectedMeshNode(node);
            Element chosenElement = null;
            VendorModel chosenModel = null;
            try {
                for (Element e : node.getElements().values()) {
                    for (MeshModel m : e.getMeshModels().values()) {
                        if (m instanceof VendorModel) {
                            chosenElement = e;
                            chosenModel = (VendorModel) m;
                            break;
                        }
                    }
                    if (chosenElement != null) break;
                }
            } catch (Exception ignore) {}

            if (chosenElement == null || chosenModel == null) {
                Log.e(TAG, "节点 " + node.getNodeName() + " 未找到 VendorModel，无法发送颜色");
                return;
            }

            // 若未绑定 AppKey，则延迟重试，避免首次点击时绑定尚未完成导致失败
            final java.util.List<Integer> bound = chosenModel.getBoundAppKeyIndexes();
            if (bound == null || bound.isEmpty()) {
                Log.w(TAG, "节点 " + node.getNodeName() + " 的 VendorModel 未绑定 AppKey，延迟重试颜色发送");
                scheduleColorSendRetry(node, color);
                return;
            }

            mViewModel.setSelectedElement(chosenElement);
            mViewModel.setSelectedModel(chosenModel);

            // 读取 opcode 并发送 Vendor 消息，负载为 JSON 字节
            SharedPreferences prefs = getSharedPreferences("AppPrefs", MODE_PRIVATE);
            String opCodeStr = prefs.getString("opCode", opCode);
            if (opCodeStr == null || opCodeStr.isEmpty()) {
                opCodeStr = "0xC0";
                Log.w(TAG, "opCode 为空或无效，使用默认 0xC0");
                prefs.edit().putString("opCode", opCodeStr).apply();
            }
            int opCodeInt = Integer.decode(opCodeStr);
            String json = "{\"color\":" + color + "}";
            byte[] payload = json.getBytes(StandardCharsets.UTF_8);
            sendVendorModelMessage(opCodeInt, payload, true);
            String nodeKey = String.valueOf(node.getUnicastAddress());
            pendingColorRetryCount.remove(nodeKey);
            Log.d(TAG, "已向节点 " + node.getNodeName() + " 发送颜色 JSON: " + json);
        } catch (Exception e) {
            Log.e(TAG, "发送颜色到节点失败", e);
        }
    }

    private void scheduleColorSendRetry(ProvisionedMeshNode node, int color) {
        if (node == null) return;
        String nodeKey = String.valueOf(node.getUnicastAddress());
        int attempt = pendingColorRetryCount.getOrDefault(nodeKey, 0);
        if (attempt >= MAX_COLOR_RETRY) {
            Log.e(TAG, "颜色发送重试达到上限(" + MAX_COLOR_RETRY + ")，放弃: " + node.getNodeName());
            pendingColorRetryCount.remove(nodeKey);
            return;
        }
        int nextAttempt = attempt + 1;
        pendingColorRetryCount.put(nodeKey, nextAttempt);
        long delay = COLOR_RETRY_BASE_DELAY_MS * nextAttempt;
        mDelayHandler.postDelayed(() -> sendColorJsonToNode(node, color), delay);
        Log.d(TAG, "计划第 " + nextAttempt + " 次颜色发送重试，延迟 " + delay + "ms，节点: " + node.getNodeName());
    }

    private void checkAndCreateMeshNetwork() {
        if (selectedNodes.size() >= 2) {
            createMeshNetwork();
        }
    }

    private void createMeshNetwork() {
        Log.d(TAG, "创建mesh网络，包含 " + selectedNodes.size() + " 个节点");

        // 为选中的节点建立mesh组网连接
        for (ProvisionedMeshNode node : selectedNodes) {
            // 确保节点有应用密钥
            if (mViewModel.getNetworkLiveData().getMeshNetwork() != null) {
                List<ApplicationKey> appKeys = mViewModel.getNetworkLiveData().getMeshNetwork().getAppKeys();
                if (!appKeys.isEmpty()) {
                    ApplicationKey appKey = appKeys.get(0);
                    if (appKey != null && !node.getAddedAppKeys().contains(new NodeKey(appKey.getKeyIndex()))) {
                        // 检查App key是否已存在于网络中，避免重复添加
                        boolean appKeyExists = false;
                        for (ApplicationKey existingKey : appKeys) {
                            if (Arrays.equals(existingKey.getKey(), appKey.getKey())) {
                                appKeyExists = true;
                                break;
                            }
                        }

                        // 只有当App key不存在时才添加到网络中
                        if (!appKeyExists) {
                            try {
                                mViewModel.getNetworkLiveData().getMeshNetwork().addAppKey(appKey);
                            } catch (IllegalArgumentException e) {
                                Log.w(TAG, "App key已存在，跳过添加: " + e.getMessage());
                            }
                        }
                    }
                }
            }
        }
    }


    /**
     * 处理数据阻塞功能
     * 对未选中的节点实施数据发送阻塞
     */
    private void handleDataBlocking() {
        // 基于选中状态维护阻塞：未选中即阻塞，选中即解除阻塞（按地址判断）
        List<ProvisionedMeshNode> allNodes = mViewModel.getNetworkLiveData().getMeshNetwork().getNodes();

        // 构建选中地址集合，便于快速判断
        final Set<Integer> selectedAddrs = new HashSet<>();
        for (ProvisionedMeshNode sn : selectedNodes) {
            if (sn != null) {
                selectedAddrs.add(sn.getUnicastAddress());
            }
        }

        // 先解除所有选中节点的阻塞
        List<ProvisionedMeshNode> toUnblock = new ArrayList<>();
        for (ProvisionedMeshNode bn : blockedNodes) {
            if (bn != null && selectedAddrs.contains(bn.getUnicastAddress())) {
                toUnblock.add(bn);
            }
        }
        for (ProvisionedMeshNode bn : toUnblock) {
            blockedNodes.remove(bn);
            Log.d(TAG, "节点 " + bn.getNodeName() + " 已解除阻塞（选中，按地址）");
        }

        // 将未选中节点加入阻塞列表（按地址），避免重复
        int newlyBlocked = 0;
        for (ProvisionedMeshNode node : allNodes) {
            if (node == null) continue;
            final int addr = node.getUnicastAddress();
            if (!selectedAddrs.contains(addr)) {
                // 检查是否已经在阻塞列表中（按地址）
                boolean exists = false;
                for (ProvisionedMeshNode bn : blockedNodes) {
                    if (bn != null && bn.getUnicastAddress() == addr) {
                        exists = true;
                        break;
                    }
                }
                if (!exists) {
                    blockedNodes.add(node);
                    newlyBlocked++;
                    Log.d(TAG, "节点 " + node.getNodeName() + " 已被阻塞（未选中，按地址）");
                }
            }
        }
        Log.d(TAG, "当前选中节点数: " + selectedNodes.size() + ", 阻塞节点数: " + blockedNodes.size());
    }

    /**
     * 确保所有在spinner中的节点都有配置
     */
    private void ensureAllNodesHaveConfiguration() {
        for (Map.Entry<Integer, ProvisionedMeshNode> entry : spinnerIndexToNodeMap.entrySet()) {
            Integer spinnerIndex = entry.getKey();
            ProvisionedMeshNode node = entry.getValue();

            String nodeKey = String.valueOf(node.getUnicastAddress());
            if (!deviceSettings.containsKey(nodeKey)) {
                // 为没有配置的节点创建默认配置
                ParameterState defaultState = createDefaultState();
                deviceSettings.put(nodeKey, defaultState);
                Log.d(TAG, "为节点 " + node.getNodeName() + " 创建默认配置 (地址: " + node.getUnicastAddress() + ", spinner索引: " + spinnerIndex + ")");
            }
        }
    }

    /**
     * 向指定节点发送其专属的波形配置
     */
    private void sendWaveJsonToNodeWithConfig(ProvisionedMeshNode node, int waveType) {
        Log.d(TAG, "向节点 " + node.getNodeName() + " 发送专属波形配置");

        // 使用节点的单播地址字符串作为键获取/保存其专属配置
        String nodeKey = String.valueOf(node.getUnicastAddress());
        ParameterState state = deviceSettings.get(nodeKey);
        if (state == null) {
            Log.w(TAG, "未找到节点 " + node.getNodeName() + " 的专属配置，创建默认配置");
            state = createDefaultState();
            deviceSettings.put(nodeKey, state);
            Log.d(TAG, "为节点 " + node.getNodeName() + " (地址: " + node.getUnicastAddress() + ") 创建并保存默认配置");
        } else {
            Log.d(TAG, "成功获取节点 " + node.getNodeName() + " 的专属配置");
        }

        // 3) 基于该节点的专属配置构造 JSON
        List<Wave> waves = new ArrayList<>();
        if (state.bandOrder != null) {
            for (int segment : state.bandOrder) {
                SegmentParams p = state.segmentParams != null ? state.segmentParams.get(segment) : null;
                if (p != null) {
                    Wave wave = new Wave();
                    wave.setTime(String.valueOf(p.durationMin));
                    wave.setType(String.valueOf((p.waveformType / 3 + 1) + 3 * (p.waveformType % 3)));
                    wave.setPusle_witdth(String.valueOf(p.pulseWidthMs / 10));
                    wave.setFrequence(String.valueOf(p.frequencyHz));
                    wave.setCount(String.valueOf(state.bandOrder.size()));
                    wave.setGear(String.valueOf(state.gear));
                    waves.add(wave);
                }
            }
        }

        VendorModelData data = new VendorModelData();
        data.setWaves(waves);
        data.setStatus(waveType);
        String json = new Gson().toJson(data);
        final String nodeKeyLog = String.valueOf(node.getUnicastAddress());
        Log.d(TAG, "为节点 " + node.getNodeName() + " 构造专属 JSON (节点键: " + nodeKeyLog + "):\n" + json);
        // ▼ 只在开始/暂停时把模式并入 JSON（经皮=0，经针=1），不发送电流
        int modeInt = (state.stimMode == STIM_MODE_ND) ? 1 : 0; // 1=经针, 0=经皮

        JsonObject root = JsonParser.parseString(json).getAsJsonObject();
        root.addProperty("mode", modeInt);
// 你要求“最后加个 model 就好”，这里与 mode 同值
        root.addProperty("model", modeInt);

        json = root.toString();
// ▲


        // 4) 设置发送目标：选中该节点、选择其包含的 VendorModel 的元素与模型
        mViewModel.setSelectedMeshNode(node);
        Element chosenElement = null;
        VendorModel chosenModel = null;
        try {
            for (Element e : node.getElements().values()) {
                for (MeshModel m : e.getMeshModels().values()) {
                    if (m instanceof VendorModel) {
                        chosenElement = e;
                        chosenModel = (VendorModel) m;
                        break;
                    }
                }
                if (chosenElement != null) break;
            }
        } catch (Exception ignore) {}

        if (chosenElement == null || chosenModel == null) {
            Log.e(TAG, "节点 " + node.getNodeName() + " 未找到已绑定的 VendorModel，无法发送");
            return;
        }

        mViewModel.setSelectedElement(chosenElement);
        mViewModel.setSelectedModel(chosenModel);

        // 5) 读取 opcode 并发送 Vendor 模型消息
        try {
            SharedPreferences prefs = getSharedPreferences("AppPrefs", MODE_PRIVATE);
            String opCodeStr = prefs.getString("opCode", opCode);
            if (opCodeStr == null || opCodeStr.isEmpty()) {
                Log.e(TAG, "opCode 为空或无效");
                return;
            }
            int opCodeInt = Integer.decode(opCodeStr);
            byte[] payload = json.getBytes(StandardCharsets.UTF_8);
            sendVendorModelMessage(opCodeInt, payload, true);
            Log.d(TAG, "成功向节点 " + node.getNodeName() + " 发送专属配置");
        } catch (Exception e) {
            Log.e(TAG, "发送到节点 " + node.getNodeName() + " 失败", e);
        }
    }

    /**
     * 测试验证设备与节点的映射关系
     */
    private void testDeviceNodeMapping() {
        Log.d(TAG, "=== 开始测试设备与节点映射关系 ===");

        // 显示当前选中的节点
        Log.d(TAG, "当前选中的节点数量: " + selectedNodes.size());
        for (int i = 0; i < selectedNodes.size(); i++) {
            ProvisionedMeshNode node = selectedNodes.get(i);
            Log.d(TAG, "节点 " + i + ": " + node.getNodeName() + " (地址: " + node.getUnicastAddress() + ")");
        }

        // 显示设备Spinner的映射关系
        Log.d(TAG, "设备Spinner映射关系:");
        for (Map.Entry<Integer, ProvisionedMeshNode> entry : spinnerIndexToNodeMap.entrySet()) {
            int index = entry.getKey();
            ProvisionedMeshNode node = entry.getValue();
            Log.d(TAG, "索引 " + index + " -> 节点: " + node.getNodeName() + " (地址: " + node.getUnicastAddress() + ")");
        }

        // 显示当前保存的设备配置
        Log.d(TAG, "当前保存的设备配置:");
        for (Map.Entry<String, ParameterState> entry : deviceSettings.entrySet()) {
            String nodeKey = entry.getKey();
            ParameterState state = entry.getValue();
            Log.d(TAG, "节点键 " + nodeKey + " 有配置: " + (state != null));
        }
    }

    /**
     * 依次向选中的节点发送专属波形参数
     * @param waveType 波形类型 (0=停止, 1=开始)
     */
    private void sendWaveJsonToSelectedNodesSequentially(int waveType) {
        if (selectedNodes.isEmpty()) {
            Log.w(TAG, "没有选中的节点，跳过发送");
            return;
        }
        // 仅在当前界面设备尚无配置时保存，避免覆盖已有专属配置
        int selectedSpinnerIndex = deviceSpinner.getSelectedItemPosition();
        if (selectedSpinnerIndex > 0) {
            ProvisionedMeshNode selNode = spinnerIndexToNodeMap.get(selectedSpinnerIndex);
            if (selNode != null) {
                String nodeKey = String.valueOf(selNode.getUnicastAddress());
                if (!deviceSettings.containsKey(nodeKey)) {
                    deviceSettings.put(nodeKey, collectCurrentState());
                    Log.d(TAG, "当前界面设备未有配置，已保存: " + nodeKey);
                } else {
                    Log.d(TAG, "当前界面设备已有配置，保持不变: " + nodeKey);
                }
            }
        }

        // 确保所有节点都有配置
        ensureAllNodesHaveConfiguration();

        // 去重：本次发送会话中已处理的地址集合（避免同一地址的不同对象导致重复发送）
        final Set<String> dispatchedNodeKeys = new HashSet<>();

        // 依次向每个选中的节点发送专属波形参数（与选中列表一致的正序）
        Handler sequentialHandler = new Handler(Looper.getMainLooper());
        for (int i = 0; i < selectedNodes.size(); i++) {
            // 正序取节点：按列表顺序发送
            final ProvisionedMeshNode node = selectedNodes.get(selectedNodes.size() - 1 -i);

            // 为每个节点添加延迟，避免消息冲突
            sequentialHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    String nodeKey = String.valueOf(node.getUnicastAddress());

                    // 去重：同一地址本次会话只发送一次
                    if (!dispatchedNodeKeys.add(nodeKey)) {
                        Log.w(TAG, "节点 " + nodeKey + " 已在本次会话发送过，跳过重复发送");
                        return;
                    }

                    // 按地址判断阻塞（避免不同对象引用导致阻塞失效）
                    boolean isBlocked = false;
                    for (ProvisionedMeshNode bn : blockedNodes) {
                        if (bn != null && bn.getUnicastAddress() == node.getUnicastAddress()) {
                            isBlocked = true;
                            break;
                        }
                    }
                    if (isBlocked) {
                        Log.w(TAG, "节点 " + nodeKey + " 被阻塞，跳过发送");
                        return;
                    }

                    // 检查节点是否有配置
                    if (!deviceSettings.containsKey(nodeKey)) {
                        Log.w(TAG, "节点 " + nodeKey + " 没有配置，跳过发送");
                        return;
                    }
                    // 发送专属波形参数给该节点
                    sendWaveJsonToNodeWithConfig(node, waveType);
                }
            }, i * 10); // 每个节点间隔10ms发送，降低冲突概率
        }
    }
}

